diff --git a/src/executors/executor.rs b/src/executors/executor.rs index d5d52dc..aaf7e50 100644 --- a/src/executors/executor.rs +++ b/src/executors/executor.rs @@ -172,6 +172,14 @@ impl Executor { self.execute_load(name, index) } + pub(crate) fn execute_replay(&mut self) -> Result<(), EngineError> { + let script = self.script.borrow(); + if let Some((name, voice)) = script.last_voice() { + self.play_voice(&name, &voice)?; + } + Ok(()) + } + pub(crate) fn execute_save(&mut self, index: i32) -> Result<(), EngineError> { if let Some(window) = self.weak.upgrade() { let script = self.script.borrow(); @@ -295,9 +303,7 @@ impl Executor { pub(crate) fn execute_save_config(&self) -> Result<(), EngineError> { let weak = self.get_weak(); - save_user_config(weak)?; - - Ok(()) + save_user_config(weak) } pub(crate) fn execute_choose(&mut self, choice: SharedString) -> Result<(), EngineError> { @@ -550,15 +556,24 @@ impl Executor { for (index, choice) in choices.iter().enumerate() { choose_branch.push((index as i32, SharedString::from(choice.0.clone()))); } - script.push_backlog("选择支".to_shared_string(), explain.to_shared_string()); + script.push_backlog( + "选择支".to_shared_string(), + explain.to_shared_string(), + None, + ); window.set_choose_branch(Rc::new(VecModel::from(choose_branch)).into()); window.set_current_choose(choices.len() as i32); } Command::Dialogue { speaker, text } => { { let mut script = self.script.borrow_mut(); + let voice = script.pre_voice(); script.set_explain(&text); - script.push_backlog(speaker.to_shared_string(), text.to_shared_string()); + script.push_backlog( + speaker.to_shared_string(), + text.replace("{nns}", "").to_shared_string(), + voice, + ); } window.set_speaker(SharedString::from(speaker)); { @@ -572,33 +587,9 @@ impl Executor { ref name, ref voice, } => { - if let Some(length) = VOICE_LENGTH.find(name) { - let volume = window.get_main_volume() / 100.0; - let voice_volume = window.get_voice_volume() / 100.0; - let character_volumes = window.get_character_volumes(); - { - let full_name = ENGINE_CONFIG.character_list().get(name).unwrap(); - for CharacterVolume { - name: ch_name, - volume: ch_volume, - } in character_volumes.iter() - { - if ch_name == full_name { - self.media_player.borrow().play_voice( - &format!( - "{}/{}/{}.ogg", - ENGINE_CONFIG.voice_path(), - name, - voice - ), - volume * voice_volume * ch_volume / 100.0, - )?; - break; - } - } - } - duration += *length.get(voice).unwrap(); - } + let mut script = self.script.borrow_mut(); + script.set_pre_voice((name.to_shared_string(), voice.to_shared_string())); + duration += self.play_voice(name, voice)?; } Command::PlayVideo(name) => { self.start_video(&name)?; @@ -635,6 +626,39 @@ impl Executor { Ok(()) } + pub(crate) fn play_voice( + &self, + name: &String, + voice: &String, + ) -> Result { + let weak = self.weak.clone(); + + if let (Some(length), Some(window)) = (VOICE_LENGTH.find(name), weak.upgrade()) { + let volume = window.get_main_volume() / 100.0; + let voice_volume = window.get_voice_volume() / 100.0; + let character_volumes = window.get_character_volumes(); + { + let full_name = ENGINE_CONFIG.character_list().get(name).unwrap(); + for CharacterVolume { + name: ch_name, + volume: ch_volume, + } in character_volumes.iter() + { + if ch_name == full_name { + self.media_player.borrow().play_voice( + &format!("{}/{}/{}.ogg", ENGINE_CONFIG.voice_path(), name, voice), + volume * voice_volume * ch_volume / 100.0, + )?; + break; + } + } + } + return Ok(*length.get(voice).unwrap()); + } + + Ok(Duration::from_secs(0)) + } + fn show_bg(&mut self, bg: &Command) -> Result<(), EngineError> { let weak = self.weak.clone(); let Command::Background { diff --git a/src/script.rs b/src/script.rs index dccc220..b8dc2fc 100644 --- a/src/script.rs +++ b/src/script.rs @@ -2,6 +2,7 @@ use crate::config::ENGINE_CONFIG; use crate::error::{EngineError, ScriptError}; use crate::media::player::PreBgm; use crate::parser::script_parser::{Command, Commands}; +use crate::ui::initialize::BackLogItem; use slint::{SharedString, ToSharedString}; use std::{ collections::{BTreeMap, HashMap, HashSet}, @@ -12,25 +13,18 @@ pub(crate) type Label = (String, String); const WINDOW_SIZE: usize = 4; -#[derive(Debug, Clone)] -pub(crate) struct BackLog { - front: SharedString, - back: SharedString, - script: SharedString, - index: usize, -} - #[derive(Debug, Clone)] pub(crate) struct Script { name: String, explain: String, backlog_offset: usize, - backlog: Vec, + backlog: Vec, commands: Vec, current_block: usize, bgm: BTreeMap, current_bgm: String, pre_bgm: PreBgm, + pre_voice: Option<(SharedString, SharedString)>, backgrounds: BTreeMap, pre_bg: Option, figures: BTreeMap, @@ -52,6 +46,7 @@ impl Script { bgm: BTreeMap::new(), current_bgm: String::new(), pre_bgm: PreBgm::None, + pre_voice: None, backgrounds: BTreeMap::new(), pre_bg: None, figures: BTreeMap::new(), @@ -110,6 +105,10 @@ impl Script { self.pre_bgm = pre_bgm; } + pub(crate) fn set_pre_voice(&mut self, pre_voice: (SharedString, SharedString)) { + self.pre_voice = Some(pre_voice); + } + pub(crate) fn set_pre_bg(&mut self, pre_bg: Option) { self.pre_bg = pre_bg; } @@ -131,7 +130,7 @@ impl Script { .push(distance, position, command); } - pub(crate) fn set_backlog(&mut self, backlog: Vec) { + pub(crate) fn set_backlog(&mut self, backlog: Vec) { self.backlog = backlog; } @@ -155,12 +154,20 @@ impl Script { self.labels.insert(label, index); } - pub(crate) fn push_backlog(&mut self, name: SharedString, text: SharedString) { - self.backlog.push(BackLog { + pub(crate) fn push_backlog( + &mut self, + name: SharedString, + text: SharedString, + voice: Option<(SharedString, SharedString)>, + ) { + let (chara, voice) = voice.unwrap_or_default(); + self.backlog.push(BackLogItem { front: name, back: text, script: self.name.to_shared_string(), - index: self.current_block, + index: self.current_block as i32, + chara, + voice, }); } @@ -168,7 +175,7 @@ impl Script { self.commands.push(command); } - pub(crate) fn backlog(&self) -> Vec<(SharedString, SharedString, i32, SharedString)> { + pub(crate) fn backlog(&self) -> Vec { let total = self.backlog.len(); if total == 0 { return vec![]; @@ -176,20 +183,18 @@ impl Script { let end = total.saturating_sub(self.backlog_offset); let start = end.saturating_sub(WINDOW_SIZE); - self.backlog[start..end] - .iter() - .map(|backlog| { - ( - backlog.back.to_shared_string(), - backlog.front.to_shared_string(), - backlog.index as i32, - backlog.script.to_shared_string(), - ) - }) - .collect() - } - - pub(crate) fn take_backlog(self) -> Vec { + self.backlog[start..end].to_vec() + } + + pub(crate) fn last_voice(&self) -> Option<(String, String)> { + let backlog = self.backlog.last().unwrap(); + if backlog.voice.is_empty() && backlog.chara.is_empty() { + return None; + } + Some((backlog.chara.to_string(), backlog.voice.to_string())) + } + + pub(crate) fn take_backlog(self) -> Vec { self.backlog } @@ -219,6 +224,10 @@ impl Script { bgm } + pub(crate) fn pre_voice(&mut self) -> Option<(SharedString, SharedString)> { + self.pre_voice.take() + } + pub(crate) fn pre_figures(&mut self) -> Option
{ self.pre_figures.take() } diff --git a/src/ui/initialize.rs b/src/ui/initialize.rs index 1010a89..757de18 100644 --- a/src/ui/initialize.rs +++ b/src/ui/initialize.rs @@ -117,6 +117,22 @@ pub(crate) async fn ui() -> Result<(), EngineError> { } }); + window.on_backlog_replay({ + let executor = executor.clone(); + move |name, voice| { + executor + .play_voice(&name.to_string(), &voice.to_string()) + .expect("Backlog resume panicked"); + } + }); + + window.on_replay_voice({ + let mut executor = executor.clone(); + move || { + executor.execute_replay().expect("Backlog resume panicked"); + } + }); + window.on_clicked({ let mut executor = executor.clone(); move || { diff --git a/ui/components/backlog.slint b/ui/components/backlog.slint index 0bd9be4..2df4ad5 100644 --- a/ui/components/backlog.slint +++ b/ui/components/backlog.slint @@ -2,15 +2,25 @@ import { CustomButton } from "common/button.slint"; import { Colors } from "../styles/colors.slint"; import { ScrollView } from "std-widgets.slint"; +export struct BackLogItem { + front: string, + back: string, + script: string, + index: int, + chara: string, + voice: string, +} + export component BackLogView { in property container-width; in property container-height; - in property <[{ front: string, back: string, script: string, index: int }]> backlogs; + in property <[BackLogItem]> backlogs; property backlog_len: backlogs.length; callback back(); callback backlog-change(int); callback backlog-jump(string, int); + callback backlog_replay(string, string); Rectangle { width: container-width; @@ -29,27 +39,24 @@ export component BackLogView { } TouchArea { - y: 0.1 * parent.height; - height: 0.9 * parent.height; - scroll-event(event) => { - if(event.delta-y == 0) { - reject - } - if(event.delta-y > 0) { - root.backlog-change(1); - } else { - root.backlog-change(-1); - } - reject - } + y: 0.1 * parent.height; + height: 0.9 * parent.height; + scroll-event(event) => { + if(event.delta-y > 0) { + root.backlog-change(1); + } else { + root.backlog-change(-1); } + reject + } + } Rectangle { width: parent.width * 0.8; height: parent.height * 0.8; for len in backlog_len : Rectangle { - property <{ front: string, back: string, script: string, index: int }> item: backlogs[len]; + property item: backlogs[len]; height: parent.height * 0.06 * 4; y: len * 4.2 * parent.height * 0.06; @@ -74,6 +81,16 @@ export component BackLogView { y: 0; } + TouchArea { + height: parent.height * 0.72; + width: parent.width * 31 / 32; + x: 0; + y: parent.height * 0.28; + clicked => { + root.backlog-replay(item.chara, item.voice); + } + } + Text { height: parent.height * 0.72; width: parent.width * 31 / 32; diff --git a/ui/components/story.slint b/ui/components/story.slint index a1a66cb..af17698 100644 --- a/ui/components/story.slint +++ b/ui/components/story.slint @@ -1,6 +1,6 @@ import { CustomButton } from "common/button.slint"; import { CustomRoundButton } from "common/round_button.slint"; -import { BackLogView } from "backlog.slint"; +import { BackLogItem, BackLogView } from "backlog.slint"; import { Colors } from "../styles/colors.slint"; export struct FigureItem { @@ -28,7 +28,7 @@ export component StoryView { in property dialogue-3; in property speaker; in property <[{ index: int, text: string }]> choose-branch; - in property <[{ front: string, back: string, script: string, index: int }]> backlogs; + in property <[BackLogItem]> backlogs; in property <{ img: image, x_offset: float, y_offset: float, zoom: float }> bg; in property <[FigureItem]> figure-items; in property current-choose: 0; @@ -45,9 +45,11 @@ export component StoryView { callback backlog(); callback backlog-change(int); callback backlog-jump(string, int); + callback backlog_replay(string, string); callback clicked(); callback choose(string); callback settings(); + callback replay_voice(); callback save-game(); callback load-game(); callback stop-video(); @@ -197,7 +199,16 @@ export component StoryView { CustomRoundButton { width: parent.height; height: parent.height; - x: parent.width * 0.73 - self.width; + x: parent.width * 0.61 - self.width; + y: 0; + text: "语音"; + clicked => { root.replay-voice(); } + } + + CustomRoundButton { + width: parent.height; + height: parent.height; + x: parent.width * 0.67 - self.width; y: 0; text: "存档"; clicked => { root.save-game(); } @@ -206,7 +217,7 @@ export component StoryView { CustomRoundButton { width: parent.height; height: parent.height; - x: parent.width * 0.79 - self.width; + x: parent.width * 0.73 - self.width; y: 0; text: "读档"; clicked => { root.load-game(); } @@ -215,7 +226,7 @@ export component StoryView { CustomRoundButton { width: parent.height; height: parent.height; - x: parent.width * 0.85 - self.width; + x: parent.width * 0.79 - self.width; y: 0; text: "自动"; is-on: is-auto; @@ -229,7 +240,7 @@ export component StoryView { CustomRoundButton { width: parent.height; height: parent.height; - x: parent.width * 0.91 - self.width; + x: parent.width * 0.85 - self.width; y: 0; text: "快进"; is-on: is-skip; @@ -240,6 +251,20 @@ export component StoryView { } } + CustomRoundButton { + width: parent.height; + height: parent.height; + x: parent.width * 0.91 - self.width; + y: 0; + text: "履历"; + clicked => { + is-backlog = !is-backlog; + is-auto = false; + is-skip = false; + root.backlog(); + } + } + CustomRoundButton { width: parent.height; height: parent.height; @@ -258,6 +283,7 @@ export component StoryView { back => { is_backlog = false; } backlog-change(i) => { root.backlog-change(i); } backlog-jump(s, i) => { root.backlog-jump(s, i); } + backlog-replay(n, v) => { root.backlog-replay(n, v) } } // 视频层(最上层) diff --git a/ui/main_window.slint b/ui/main_window.slint index badbabf..b1bfbbd 100644 --- a/ui/main_window.slint +++ b/ui/main_window.slint @@ -4,6 +4,7 @@ import { LoadView } from "components/load.slint"; import { ExItem, ExtraView } from "components/extra.slint"; import { SettingsView } from "components/main_config.slint"; import { StoryView, FigureItem } from "components/story.slint"; +import { BackLogItem} from "components/backlog.slint"; import { CharacterVolume } from "components/config/volume.slint"; export component MainWindow inherits Window { @@ -16,7 +17,7 @@ export component MainWindow inherits Window { in property dialogue-2; in property dialogue-3; in property speaker; - in property <[{ front: string, back: string, script: string, index: int }]> backlogs; + in property <[BackLogItem]> backlogs; in property<[{ index: int, text: string }]> choose-branch; in property <{ img: image, x_offset: float, y_offset: float, zoom: float }> bg; in property <[FigureItem]> figure-items; @@ -65,16 +66,19 @@ export component MainWindow inherits Window { root.toggle-fullscreen(); accept } else if (root.is-video) { - // 视频播放时屏蔽除 F11 之外的所有快捷键 accept } else if ((event.text == Key.F2) && current-screen == 2) { is-auto = !is-auto; root.auto-play(true); accept } else if ((event.text == Key.F3) && current-screen == 2) { - is-auto = !is-skip; + is-skip = !is-skip; root.skip-play(true); accept + } else if ((event.text == Key.F4) && current-screen == 2) { + is-backlog = !is-backlog; + root.backlog(); + accept } else if ((event.text == Key.F6) && current-screen == 2) { esc_story(3); accept @@ -182,8 +186,10 @@ export component MainWindow inherits Window { } backlog-change(i) => { root.backlog-change(i); } backlog-jump(s, i) => { root.backlog-jump(s, i); } + backlog-replay(n, v) => { root.backlog-replay(n, v) } clicked => { root.clicked(); } choose(text) => { root.choose(text); } + replay-voice => { root.replay-voice(); } save-game => { esc_story(3); } @@ -224,7 +230,6 @@ export component MainWindow inherits Window { } } - // 回调函数保持不变 callback auto-play(bool); callback skip-play(bool); callback clicked(); @@ -236,7 +241,9 @@ export component MainWindow inherits Window { callback backlog(); callback backlog-change(int); callback backlog-jump(string, int); + callback backlog_replay(string, string); callback choose(string); + callback replay-voice(); callback save(int); callback load(string, int); callback get-ex();