Skip to content

Commit 6a56215

Browse files
committed
[*] refactor
1 parent a245f3a commit 6a56215

File tree

7 files changed

+256
-23
lines changed

7 files changed

+256
-23
lines changed

lib/video-editor/src/commands/filter.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use super::command::Command;
22
use crate::{
33
Error, Result,
44
filters::traits::{
5-
AudioFilter, AudioFilterWrapper, ImageFilterWrapper, SubtitleFilter,
6-
SubtitleFilterWrapper, VideoFilter, VideoFilterWrapper,
5+
AudioFilter, AudioFilterWrapper, ImageFilterWrapper, SubtitleFilter, SubtitleFilterWrapper,
6+
VideoFilter, VideoFilterWrapper,
77
},
88
tracks::manager::Manager,
99
};
@@ -78,11 +78,7 @@ impl AddFilterCommand {
7878
}
7979
}
8080

81-
pub fn new_image(
82-
track_index: usize,
83-
segment_index: usize,
84-
filter: ImageFilterWrapper,
85-
) -> Self {
81+
pub fn new_image(track_index: usize, segment_index: usize, filter: ImageFilterWrapper) -> Self {
8682
Self {
8783
track_index,
8884
segment_index,

todo.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
## 问题
2+
- 轨道中粘贴filter到所有segment,没有更新右边的滤镜列表
23

34
## 待验证
45
- 视频轨道分离字幕

wayshot/src/logic/popup_action.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,16 @@ pub fn init(ui: &AppWindow) {
303303
let index = user_data.parse::<i32>().unwrap();
304304
global_ve_filter!(ui).invoke_copy_filter(index);
305305
}
306+
"video-editor-cut-filter" => {
307+
let index = user_data.parse::<i32>().unwrap();
308+
global_ve_filter!(ui).invoke_cut_filter(index);
309+
}
310+
"video-editor-paste-filter" => {
311+
global_ve_filter!(ui).invoke_paste_filter();
312+
}
313+
"video-editor-refresh-filter-list" => {
314+
global_ve_filter!(ui).invoke_refresh_filter_list();
315+
}
306316
_ => log::warn!("Unknown popup action: {action}"),
307317
}
308318
});

wayshot/src/logic/video_editor/filters/filter.rs

Lines changed: 196 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
},
1111
conversion::{
1212
audio_filter_to_json_detail, image_filter_to_json_detail,
13-
subtitle_filter_to_json_detail, video_filter_to_json_detail,
13+
subtitle_filter_to_json_detail, track_to_filter_type, video_filter_to_json_detail,
1414
},
1515
project::{MARKED_FILTERS_ID, PRESET_FILTERS_ID},
1616
segment::refresh_affected_segments,
@@ -130,13 +130,16 @@ pub fn init(ui: &AppWindow) {
130130
ve_filter_cb!(remove_filter, ui, index);
131131
ve_filter_cb!(toggle_filter, ui, index);
132132
ve_filter_cb!(copy_filter, ui, index);
133+
ve_filter_cb!(cut_filter, ui, index);
134+
ve_filter_cb!(paste_filter, ui);
133135
ve_filter_cb!(add_filter, ui, filter_name);
134136
ve_filter_cb!(create_preset_filter, ui, name);
135137
ve_filter_cb!(add_preset_filter, ui, filter);
136138
ve_filter_cb!(remove_preset_filter, ui, index, filter_type);
137139
ve_filter_cb!(add_cache_preset_filter, ui, filter);
138140
ve_filter_cb!(remove_cache_preset_filter, ui, filter);
139141
ve_filter_cb!(toggle_mark_filter, ui, filter);
142+
ve_filter_cb!(refresh_filter_list, ui);
140143
}
141144

142145
fn inner_init(ui: &AppWindow) {
@@ -533,6 +536,198 @@ pub fn copy_filter(ui: &AppWindow, index: i32) {
533536
}
534537
}
535538

539+
pub fn cut_filter(ui: &AppWindow, index: i32) {
540+
let merged_index = index as usize;
541+
let selected_segments = get_selected_segment_indices(ui);
542+
let Some((track_idx, seg_idx)) = selected_segments.last() else {
543+
return;
544+
};
545+
546+
let Some((filter_type, filter_index)) =
547+
get_filter_type_and_local_index(*track_idx, *seg_idx, merged_index)
548+
else {
549+
return;
550+
};
551+
552+
let filter_data: Option<(bool, String, String)> = with_history_manager(|state| {
553+
let track = state.tracks_manager.get(*track_idx)?;
554+
let segment = track.get_segment(*seg_idx).ok()?;
555+
556+
match filter_type {
557+
FilterType::Video => segment.video_filters.get(filter_index).map(|f| {
558+
let detail = video_filter_to_json_detail(&f.inner);
559+
(f.enabled(), f.inner.name().to_string(), detail)
560+
}),
561+
FilterType::Audio => segment.audio_filters.get(filter_index).map(|f| {
562+
let detail = audio_filter_to_json_detail(&f.inner);
563+
(f.enabled(), f.inner.name().to_string(), detail)
564+
}),
565+
FilterType::Subtitle => segment.subtitle_filters.get(filter_index).map(|f| {
566+
let detail = subtitle_filter_to_json_detail(&f.inner);
567+
(f.enabled(), f.inner.name().to_string(), detail)
568+
}),
569+
FilterType::Image => segment.image_filters.get(filter_index).map(|f| {
570+
let detail = image_filter_to_json_detail(&f.inner);
571+
(f.enabled(), f.inner.name().to_string(), detail)
572+
}),
573+
}
574+
});
575+
576+
let filter_name = match filter_data {
577+
Some((enabled, name, detail)) => {
578+
let segment_filter = UISegmentFilter {
579+
ty: filter_type.into(),
580+
enabled,
581+
name: SharedString::from(name.clone()),
582+
detail: SharedString::from(detail),
583+
};
584+
global_ve_filter!(ui).set_cache_copied_filter(segment_filter);
585+
name
586+
}
587+
None => return,
588+
};
589+
590+
let mut batch_command = BatchCommand::new(format!("Cut filter '{}'", filter_name));
591+
592+
for (track_idx, seg_idx) in &selected_segments {
593+
let Some((ft, fi)) = get_filter_type_and_local_index(*track_idx, *seg_idx, merged_index)
594+
else {
595+
continue;
596+
};
597+
598+
if ft != filter_type {
599+
continue;
600+
}
601+
602+
let command = match ft {
603+
FilterType::Video => Box::new(RemoveFilterCommand::new_video(*track_idx, *seg_idx, fi)),
604+
FilterType::Audio => Box::new(RemoveFilterCommand::new_audio(*track_idx, *seg_idx, fi)),
605+
FilterType::Subtitle => {
606+
Box::new(RemoveFilterCommand::new_subtitle(*track_idx, *seg_idx, fi))
607+
}
608+
FilterType::Image => Box::new(RemoveFilterCommand::new_image(*track_idx, *seg_idx, fi)),
609+
};
610+
batch_command.add_command(command);
611+
}
612+
613+
let result = with_history_manager(|state| {
614+
state
615+
.history_manager
616+
.execute(&mut state.tracks_manager, Box::new(batch_command))
617+
});
618+
619+
match result {
620+
Ok(execute_result) => {
621+
sync_manager_to_ui(ui);
622+
refresh_affected_segments(ui, execute_result.affected_segments);
623+
refresh_preview(ui);
624+
625+
global_ve_filter!(ui).set_selected_filter_index(-1);
626+
global_store!(ui).set_video_editor_segment_filter_flag(
627+
!global_store!(ui).get_video_editor_segment_filter_flag(),
628+
);
629+
crate::toast_success!(ui, format!("Cut filter: {}", filter_name));
630+
}
631+
Err(e) => crate::toast_warn!(ui, format!("Failed to cut filter: {}", e)),
632+
}
633+
}
634+
635+
pub fn paste_filter(ui: &AppWindow) {
636+
let cached_filter = global_ve_filter!(ui).get_cache_copied_filter();
637+
let filter_name = cached_filter.name.to_string();
638+
639+
if filter_name.is_empty() {
640+
crate::toast_warn!(ui, "No filter copied to paste");
641+
return;
642+
}
643+
644+
let selected_segments = get_selected_segment_indices(ui);
645+
let Some((track_idx, _seg_idx)) = selected_segments.last() else {
646+
return;
647+
};
648+
649+
let track_type: Option<FilterType> = with_history_manager(|state| {
650+
let track = state.tracks_manager.get(*track_idx)?;
651+
Some(track_to_filter_type(&track))
652+
});
653+
654+
let Some(track_type) = track_type else {
655+
return;
656+
};
657+
658+
let copied_type: FilterType = cached_filter.ty.into();
659+
660+
// Validate compatibility between copied filter type and track type
661+
if !is_filter_compatible(&copied_type, &track_type) {
662+
crate::toast_warn!(
663+
ui,
664+
format!(
665+
"Cannot paste {} filter to {} track",
666+
copied_type.to_string(),
667+
track_type.to_string()
668+
)
669+
);
670+
return;
671+
}
672+
673+
let paste_type = copied_type;
674+
let filter_detail = cached_filter.detail.to_string();
675+
let enabled = cached_filter.enabled;
676+
677+
let mut batch_command = BatchCommand::new(format!("Paste filter '{}'", filter_name));
678+
679+
for (track_idx, seg_idx) in &selected_segments {
680+
if let Some(cmd) = create_filter_command_with_detail(
681+
*track_idx,
682+
*seg_idx,
683+
paste_type.clone(),
684+
&filter_name,
685+
&filter_detail,
686+
enabled,
687+
) {
688+
batch_command.add_command(cmd);
689+
}
690+
}
691+
692+
let result = with_history_manager(|state| {
693+
state
694+
.history_manager
695+
.execute(&mut state.tracks_manager, Box::new(batch_command))
696+
});
697+
698+
match result {
699+
Ok(execute_result) => {
700+
sync_manager_to_ui(ui);
701+
refresh_affected_segments(ui, execute_result.affected_segments);
702+
refresh_preview(ui);
703+
704+
global_ve_filter!(ui).set_selected_filter_index(-1);
705+
global_ve_filter!(ui).invoke_refresh_filter_list();
706+
crate::toast_success!(ui, format!("Pasted filter: {}", filter_name));
707+
}
708+
Err(e) => crate::toast_warn!(ui, format!("Failed to paste filter: {}", e)),
709+
}
710+
}
711+
712+
pub fn refresh_filter_list(ui: &AppWindow) {
713+
global_store!(ui).set_video_editor_segment_filter_flag(
714+
!global_store!(ui).get_video_editor_segment_filter_flag(),
715+
);
716+
}
717+
718+
fn is_filter_compatible(filter_type: &FilterType, track_type: &FilterType) -> bool {
719+
match (filter_type, track_type) {
720+
(FilterType::Video, FilterType::Video) => true,
721+
(FilterType::Video, FilterType::Image) => true,
722+
(FilterType::Image, FilterType::Image) => true,
723+
(FilterType::Image, FilterType::Video) => true,
724+
(FilterType::Audio, FilterType::Audio) => true,
725+
(FilterType::Audio, FilterType::Video) => true,
726+
(FilterType::Subtitle, FilterType::Subtitle) => true,
727+
_ => false,
728+
}
729+
}
730+
536731
fn add_filter(ui: &AppWindow, entry: UIFilterEntry) {
537732
let filter_name = entry.name.to_string();
538733
let filter_type: FilterType = entry.ty.into();

wayshot/src/logic/video_editor/track.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -702,12 +702,11 @@ fn video_editor_paste_filter_to_track(ui: &AppWindow, index: i32) {
702702
sync_manager_to_ui(ui);
703703
refresh_affected_segments(ui, execute_result.affected_segments);
704704
refresh_preview(ui);
705+
706+
global_ve_filter!(ui).invoke_refresh_filter_list();
705707
crate::toast_success!(
706708
ui,
707-
format!(
708-
"Pasted filter '{}' to {} segments",
709-
filter_name, segment_count
710-
)
709+
format!("Pasted filter '{filter_name}' to {segment_count} segments")
711710
);
712711
}
713712
Err(e) => crate::toast_warn!(ui, format!("Failed to paste filter: {}", e)),
@@ -742,6 +741,10 @@ fn video_editor_remove_all_filters_from_track(ui: &AppWindow, index: i32) {
742741
match result {
743742
Ok(_execute_result) => {
744743
sync_manager_to_ui(ui);
744+
refresh_preview(ui);
745+
global_store!(ui).set_video_editor_segment_filter_flag(
746+
!global_store!(ui).get_video_editor_segment_filter_flag(),
747+
);
745748
crate::toast_success!(ui, format!("Removed all filters from track {}", index));
746749
}
747750
Err(e) => crate::toast_warn!(ui, format!("Failed to remove filters: {}", e)),
@@ -1049,9 +1052,6 @@ pub fn is_track_locked(ui: &AppWindow, track_index: i32) -> bool {
10491052
.unwrap_or(false)
10501053
}
10511054

1052-
/// Reset all editor selection state when adding/removing tracks.
1053-
/// This clears the current edited track index, selected filter index,
1054-
/// selected tracks list, and selected segments list.
10551055
pub fn reset_editor_selection_state(ui: &AppWindow) {
10561056
global_store!(ui).set_video_editor_current_edited_track_index(-1);
10571057
global_ve_filter!(ui).set_selected_filter_index(-1);

wayshot/ui/panel/desktop/video-editor/filter.slint

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,10 @@ export global VEFilter {
292292
callback remove-filter(index: int);
293293
callback toggle-filter(index: int);
294294
callback copy-filter(index: int);
295+
callback cut-filter(index: int);
296+
callback paste-filter();
295297
callback add-filter(entry: FilterEntry);
298+
callback refresh-filter-list();
296299

297300
callback create-preset-filter(name: string);
298301
callback add-preset-filter(filter: PresetFilter);
@@ -301,7 +304,6 @@ export global VEFilter {
301304
callback remove-cache-preset-filter(filter: SegmentFilter);
302305

303306
callback toggle-mark-filter(filter: FilterEntry); // 收藏和取消收藏滤镜
304-
305307
callback video-editor-update-font-style-from-track(track-index: int);
306308

307309
// Video/Image shared filter callbacks - filter-type as last parameter

wayshot/ui/panel/desktop/video-editor/right-panel/filter/filter.slint

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,37 @@ component SegmentFilterList inherits Rectangle {
214214
}
215215
}
216216

217-
IconBtn {
218-
icon: Icons.preset-light;
219-
icon-size: Theme.icon-size * 0.8;
220-
tip: Logic.tr("create preset filter");
221-
is-show-tip: true;
217+
HorizontalLayout {
218+
spacing: Theme.spacing * 2;
222219

223-
clicked => {
224-
preset-mode = !preset-mode;
220+
IconBtn {
221+
icon: Icons.preset-light;
222+
icon-size: Theme.icon-size * 0.8;
223+
tip: Logic.tr("create preset filter");
224+
is-show-tip: true;
225+
226+
clicked => {
227+
preset-mode = !preset-mode;
228+
}
229+
}
230+
231+
IconBtn {
232+
icon: Icons.more-v-light;
233+
234+
clicked => {
235+
PopupActionSetting.show(self.absolute-position.x, self.absolute-position.y + self.preferred-height, [
236+
{
237+
icon: Icons.paste-light,
238+
text: Logic.tr("Paste"),
239+
action: "video-editor-paste-filter",
240+
},
241+
{
242+
icon: Icons.arrow-up-light,
243+
text: Logic.tr("refresh"),
244+
action: "video-editor-refresh-filter-list",
245+
},
246+
]);
247+
}
225248
}
226249
}
227250
}
@@ -355,6 +378,12 @@ component SegmentFilterList inherits Rectangle {
355378
action: "video-editor-copy-filter",
356379
user-data: index,
357380
},
381+
{
382+
icon: Icons.cut-light,
383+
text: Logic.tr("Cut"),
384+
action: "video-editor-cut-filter",
385+
user-data: index,
386+
},
358387
{
359388
icon: Icons.clear-light,
360389
text: Logic.tr("Remove"),

0 commit comments

Comments
 (0)