From 0819a3aa2cee92752d1cb9b5022a7dfae630d178 Mon Sep 17 00:00:00 2001 From: wszhdshys <136774049+wszhdshys@users.noreply.github.com> Date: Fri, 24 Jul 2026 23:54:10 +0800 Subject: [PATCH 1/8] 1 --- src/script.rs | 5 +++++ ui/components/story.slint | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/script.rs b/src/script.rs index 14d367f..ca560e1 100644 --- a/src/script.rs +++ b/src/script.rs @@ -91,6 +91,7 @@ pub(crate) struct Script { backlog: Vec, commands: Vec, current_block: usize, + read_index: usize, current_bgm: String, pre_voice: Option<(SharedString, SharedString)>, timeline: Timeline, @@ -109,6 +110,7 @@ impl Script { backlog: Vec::new(), commands: Vec::new(), current_block: 0, + read_index: 0, current_bgm: String::new(), pre_voice: None, timeline: Timeline::default(), @@ -125,6 +127,9 @@ impl Script { pub(crate) fn next_command(&mut self) -> Option<&Commands> { let command = self.commands.get(self.current_block); + if self.read_index <= self.current_block { + self.read_index += 1; + } self.current_block += 1; command } diff --git a/ui/components/story.slint b/ui/components/story.slint index c73e5a0..adb0b4e 100644 --- a/ui/components/story.slint +++ b/ui/components/story.slint @@ -65,6 +65,8 @@ export component StoryView { scroll-event(event) => { if (root.is-video) { accept + } else if (event.delta-y == 0) { + accept } else if (event.delta-y > 0) { root.backlog(); is_backlog = true; From d3692ad452bfae690b0924416e230c529ce6ccb9 Mon Sep 17 00:00:00 2001 From: nanase <1925792291@qq.com> Date: Sat, 25 Jul 2026 17:13:01 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E5=89=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- savedata/data.toml | 8 ++++ savedata/extra.toml | 2 - savedata/user.toml | 1 + src/config/cg.rs | 15 ------- src/config/extra.rs | 49 --------------------- src/config/mod.rs | 1 - src/data/cg.rs | 16 +++++++ src/data/mod.rs | 4 ++ src/data/read.rs | 0 src/data/user.rs | 52 ++++++++++++++++++++++ src/executors/executor.rs | 92 +++++++++++++++++++++++++-------------- src/executors/mod.rs | 31 +++++-------- src/main.rs | 1 + src/script.rs | 8 ++-- src/ui/initialize.rs | 27 +++++++----- ui/components/story.slint | 16 +------ ui/main_window.slint | 20 +++------ 17 files changed, 183 insertions(+), 160 deletions(-) create mode 100644 savedata/data.toml delete mode 100644 savedata/extra.toml delete mode 100644 src/config/extra.rs create mode 100644 src/data/cg.rs create mode 100644 src/data/mod.rs create mode 100644 src/data/read.rs create mode 100644 src/data/user.rs diff --git a/savedata/data.toml b/savedata/data.toml new file mode 100644 index 0000000..8b77e10 --- /dev/null +++ b/savedata/data.toml @@ -0,0 +1,8 @@ +[cg] +cg = [1022] + +[read] +read = [ + {name = "ky01", index = 4}, + {name = "ky02", index = 6}, +] diff --git a/savedata/extra.toml b/savedata/extra.toml deleted file mode 100644 index d44936b..0000000 --- a/savedata/extra.toml +++ /dev/null @@ -1,2 +0,0 @@ -[cg] -cg = [1022] diff --git a/savedata/user.toml b/savedata/user.toml index 3bb0f67..2c9b4f7 100644 --- a/savedata/user.toml +++ b/savedata/user.toml @@ -7,6 +7,7 @@ speed = 50.0 opacity = 0.8 is_bold = false show_shadow = true +font = "PingFang SC" [volume] main = 100.0 diff --git a/src/config/cg.rs b/src/config/cg.rs index 32bb301..ba2325b 100644 --- a/src/config/cg.rs +++ b/src/config/cg.rs @@ -10,21 +10,6 @@ lazy_static::lazy_static! { pub(crate) static ref CG_CONFIG: CgConfig = load_cg(); } -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct CgMap { - cg: Vec, -} - -impl CgMap { - pub(crate) fn new(cg: Vec) -> Self { - Self { cg } - } - - pub(crate) fn cg(self) -> Vec { - self.cg - } -} - #[derive(Debug, Deserialize, Serialize, Clone)] struct Length { name: String, diff --git a/src/config/extra.rs b/src/config/extra.rs deleted file mode 100644 index f8a96d8..0000000 --- a/src/config/extra.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::config::cg::CG_CONFIG; -use crate::config::{cg::CgMap, ENGINE_CONFIG}; -use crate::error::{EngineError, SaveError}; -use crate::executors::executor::Executor; -use serde::{Deserialize, Serialize}; -use std::cell::RefCell; -use std::fs; -use std::rc::Rc; - -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct ExtraConfig { - cg: CgMap, -} - -impl ExtraConfig { - pub(crate) fn cg(self) -> Vec { - self.cg.cg() - } -} - -impl Executor { - pub(crate) fn load_extra(&mut self) { - let extra_config = load_extra_config(); - self.set_cg(extra_config.cg()); - } -} - -fn load_extra_config() -> ExtraConfig { - if let Ok(content) = fs::read_to_string(format!("{}/extra.toml", ENGINE_CONFIG.save_path())) { - toml::from_str(&content).unwrap() - } else { - let num = CG_CONFIG.length() / 64 + 1; - ExtraConfig { - cg: CgMap::new(vec![0; num]), - } - } -} - -pub(crate) fn save_extra_config(cg: Rc>>) -> Result<(), EngineError> { - let cg = cg.borrow(); - let path = format!("{}/extra.toml", ENGINE_CONFIG.save_path()); - let content = toml::to_string(&ExtraConfig { - cg: CgMap::new(cg.clone()), - }) - .map_err(SaveError::from)?; - fs::write(&path, content).map_err(|e| SaveError::Write { path, source: e })?; - - Ok(()) -} diff --git a/src/config/mod.rs b/src/config/mod.rs index 15b4940..c36b127 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -11,7 +11,6 @@ pub(crate) mod save_load; pub(crate) mod cg; pub(crate) mod character_volume; -pub(crate) mod extra; pub(crate) mod font; pub(crate) mod system; pub(crate) mod text; diff --git a/src/data/cg.rs b/src/data/cg.rs new file mode 100644 index 0000000..52582b7 --- /dev/null +++ b/src/data/cg.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct CgMap { + cg: Vec, +} + +impl CgMap { + pub(crate) fn new(cg: Vec) -> Self { + Self { cg } + } + + pub(crate) fn cg(&self) -> Vec { + self.cg.clone() + } +} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..ee954dd --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod user; + +pub(crate) mod cg; +pub(crate) mod read; \ No newline at end of file diff --git a/src/data/read.rs b/src/data/read.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/data/user.rs b/src/data/user.rs new file mode 100644 index 0000000..28ec96e --- /dev/null +++ b/src/data/user.rs @@ -0,0 +1,52 @@ +use crate::config::{cg::CG_CONFIG, ENGINE_CONFIG}; +use crate::error::{EngineError, SaveError}; +use crate::executors::executor::Executor; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::fs; +use std::rc::Rc; +use crate::data::cg::CgMap; + +lazy_static::lazy_static! { + pub(crate) static ref USER_DATA: UserData = load_user_data(); +} + +#[derive(Debug, Deserialize, Serialize)] +pub(crate) struct UserData { + cg: CgMap, +} + +impl UserData { + pub(crate) fn cg(&self) -> Vec { + self.cg.cg() + } +} + +impl Executor { + pub(crate) fn load_cg(&mut self) { + self.set_cg(USER_DATA.cg()); + } +} + +fn load_user_data() -> UserData { + if let Ok(content) = fs::read_to_string(format!("{}/data.toml", ENGINE_CONFIG.save_path())) { + toml::from_str(&content).unwrap() + } else { + let num = CG_CONFIG.length() / 64 + 1; + UserData { + cg: CgMap::new(vec![0; num]), + } + } +} + +pub(crate) fn save_user_data(cg: Rc>>) -> Result<(), EngineError> { + let cg = cg.borrow(); + let path = format!("{}/data.toml", ENGINE_CONFIG.save_path()); + let content = toml::to_string(&UserData { + cg: CgMap::new(cg.clone()), + }) + .map_err(SaveError::from)?; + fs::write(&path, content).map_err(|e| SaveError::Write { path, source: e })?; + + Ok(()) +} diff --git a/src/executors/executor.rs b/src/executors/executor.rs index a1b816d..8128b5a 100644 --- a/src/executors/executor.rs +++ b/src/executors/executor.rs @@ -1,13 +1,11 @@ use crate::config::cg::get_cg; use crate::config::{ - cg::CG_CONFIG, extra::save_extra_config, figure::FIGURE_CONFIG, save_load::SaveData, + cg::CG_CONFIG, figure::FIGURE_CONFIG, save_load::SaveData, user::save_user_config, voice::VOICE_LENGTH, ENGINE_CONFIG, }; +use crate::data::user::save_user_data; use crate::error::{EngineError, SaveError}; -use crate::executors::{ - delay_executor::{DelayChannels, DelayTX}, - text_executor::{DisplayText, TextTX}, -}; +use crate::executors::{delay_executor::{DelayChannels, DelayTX}, text_executor::{DisplayText, TextTX}, Status}; use crate::media::{ player::{MediaPlayer, PreBgm, PreBgm::Play}, video_player::{VideoContext, VideoPlayer}, @@ -82,6 +80,7 @@ pub(crate) struct Executor { text_tx: Option, auto_tx: Option>, delay_channels: Option, + status_channel: Option<(Sender<()>, Sender<()>)>, } impl Executor { @@ -106,6 +105,7 @@ impl Executor { text_tx: None, auto_tx: None, delay_channels: None, + status_channel: None, }) } @@ -138,12 +138,24 @@ impl Executor { }); } + pub(crate) fn set_status_channel(&mut self, auto_tx: Sender<()>, skip_tx: Sender<()>) { + self.status_channel = Some((auto_tx, skip_tx)); + } + + pub(crate) fn get_status_channel(&self) -> Option<(Sender<()>, Sender<()>)> { + if let Some((auto_tx, skip_tx)) = self.status_channel.as_ref() { + return Some((auto_tx.clone(), skip_tx.clone())) + } + None + } + pub(crate) fn unlock(&mut self, index: usize) { let mut cg = self.cg.borrow_mut(); cg[index / 64] |= 1u64 << index; } - pub(crate) fn execute_backlog(&self) -> Result<(), EngineError> { + pub(crate) fn execute_backlog(&mut self) -> Result<(), EngineError> { + self.execute_status(Status::Normal, false)?; if let Some(window) = self.weak.upgrade() { let script = self.script.borrow(); let backlog = script.backlog(); @@ -173,6 +185,7 @@ impl Executor { } pub(crate) fn execute_replay(&mut self) -> Result<(), EngineError> { + self.execute_status(Status::Normal, false)?; let script = self.script.borrow(); if let Some((name, voice)) = script.last_voice() { self.play_voice(&name, &voice)?; @@ -329,34 +342,49 @@ impl Executor { Ok(()) } - pub(crate) fn execute_auto(&mut self, tx: Sender<()>, source: bool) -> Result<(), EngineError> { - if let Some(window) = self.weak.upgrade() { - if source { - self.auto_tx - .clone() - .unwrap() - .try_send(Duration::from_secs(1))?; - tx.try_send(())?; - } else { - if window.get_is_auto() { - tx.try_send(())?; - } - window.set_is_auto(false); - } - } - - Ok(()) - } + pub(crate) fn execute_status(&mut self, status: Status, symbol: bool) -> Result<(), EngineError> { + let (auto_tx, skip_tx) = self.get_status_channel().unwrap(); - pub(crate) fn execute_skip(&mut self, tx: Sender<()>, source: bool) -> Result<(), EngineError> { if let Some(window) = self.weak.upgrade() { - if source { - tx.try_send(())?; - } else { - if window.get_is_skip() { - tx.try_send(())?; + match status { + Status::Auto => { + if symbol { + window.set_is_auto(false); + } else { + if window.get_is_skip() { + skip_tx.try_send(())?; + window.set_is_skip(false); + } + window.set_is_auto(true); + self.auto_tx + .clone() + .unwrap() + .try_send(Duration::from_secs(1))?; + } + auto_tx.try_send(())?; + } + Status::Skip => { + if symbol { + window.set_is_skip(false); + } else { + if window.get_is_auto() { + auto_tx.try_send(())?; + window.set_is_auto(false); + } + window.set_is_skip(true); + } + skip_tx.try_send(())?; + } + Status::Normal => { + if window.get_is_auto() { + auto_tx.try_send(())?; + window.set_is_auto(false); + } + if window.get_is_skip() { + skip_tx.try_send(())?; + window.set_is_skip(false); + } } - window.set_is_skip(false); } } @@ -609,7 +637,7 @@ impl Executor { let path = if *is_cg { if let Some((index, _)) = CG_CONFIG.find_by_name(name) { self.unlock(*index); - save_extra_config(self.cg.clone())?; + save_user_data(self.cg.clone())?; } ENGINE_CONFIG.cg_path() } else { diff --git a/src/executors/mod.rs b/src/executors/mod.rs index 81e0837..904cd66 100644 --- a/src/executors/mod.rs +++ b/src/executors/mod.rs @@ -3,7 +3,6 @@ use crate::executors::{ auto_executor::AutoExecutor, delay_executor::DelayExecutor, executor::Executor, skip_executor::SkipExecutor, text_executor::TextExecutor, }; -use tokio::sync::mpsc::Sender; pub(crate) mod auto_executor; pub(crate) mod delay_executor; @@ -11,9 +10,13 @@ pub(crate) mod executor; pub(crate) mod skip_executor; pub(crate) mod text_executor; -pub(crate) struct ExecutorTX { - auto_tx: Sender<()>, - skip_tx: Sender<()>, +pub(crate) enum Status { + Auto, + Skip, + Normal, +} + +pub(crate) struct ExecutorCluster { _text_executor: TextExecutor, _auto_executor: AutoExecutor, _skip_executor: SkipExecutor, @@ -22,17 +25,7 @@ pub(crate) struct ExecutorTX { _loop_move_executor: DelayExecutor, } -impl ExecutorTX { - pub(crate) fn auto_tx(&self) -> Sender<()> { - self.auto_tx.clone() - } - - pub(crate) fn skip_tx(&self) -> Sender<()> { - self.skip_tx.clone() - } -} - -pub(crate) fn load_data(executor: &mut Executor) -> Result { +pub(crate) fn load_data(executor: &mut Executor) -> Result { let (mut text_executor, text_tx) = TextExecutor::new(executor.get_weak()); executor.set_text_tx(text_tx); @@ -70,17 +63,17 @@ pub(crate) fn load_data(executor: &mut Executor) -> Result, commands: Vec, current_block: usize, - read_index: usize, + read_block: usize, current_bgm: String, pre_voice: Option<(SharedString, SharedString)>, timeline: Timeline, @@ -110,7 +110,7 @@ impl Script { backlog: Vec::new(), commands: Vec::new(), current_block: 0, - read_index: 0, + read_block: 0, current_bgm: String::new(), pre_voice: None, timeline: Timeline::default(), @@ -127,8 +127,8 @@ impl Script { pub(crate) fn next_command(&mut self) -> Option<&Commands> { let command = self.commands.get(self.current_block); - if self.read_index <= self.current_block { - self.read_index += 1; + if self.read_block <= self.current_block { + self.read_block += 1; } self.current_block += 1; command diff --git a/src/ui/initialize.rs b/src/ui/initialize.rs index 64b9c32..5e341fb 100644 --- a/src/ui/initialize.rs +++ b/src/ui/initialize.rs @@ -1,5 +1,5 @@ use crate::error::EngineError; -use crate::executors::{executor::Executor, load_data}; +use crate::executors::{executor::Executor, load_data, Status}; slint::include_modules!(); pub(crate) async fn ui() -> Result<(), EngineError> { @@ -8,7 +8,7 @@ pub(crate) async fn ui() -> Result<(), EngineError> { let mut executor = Executor::new(weak)?; - let executor_tx = load_data(&mut executor)?; + let _executor_cluster = load_data(&mut executor)?; let mut is_fullscreen = false; let weak_for_fullscreen = executor.get_weak(); @@ -95,7 +95,7 @@ pub(crate) async fn ui() -> Result<(), EngineError> { }); window.on_backlog({ - let executor = executor.clone(); + let mut executor = executor.clone(); move || { executor.execute_backlog().expect("Backlog panicked"); } @@ -144,22 +144,27 @@ pub(crate) async fn ui() -> Result<(), EngineError> { window.on_auto_play({ let mut executor = executor.clone(); - let tx = executor_tx.auto_tx(); - move |source| { - let tx = tx.clone(); + move |symbol| { executor - .execute_auto(tx, source) + .execute_status(Status::Auto, symbol) .expect("TODO: panic message"); } }); window.on_skip_play({ let mut executor = executor.clone(); - let tx = executor_tx.skip_tx(); - move |source| { - let tx = tx.clone(); + move |symbol| { + executor + .execute_status(Status::Skip, symbol) + .expect("TODO: panic message"); + } + }); + + window.on_normal_play({ + let mut executor = executor.clone(); + move || { executor - .execute_skip(tx, source) + .execute_status(Status::Normal, false) .expect("TODO: panic message"); } }); diff --git a/ui/components/story.slint b/ui/components/story.slint index adb0b4e..993d979 100644 --- a/ui/components/story.slint +++ b/ui/components/story.slint @@ -65,8 +65,6 @@ export component StoryView { scroll-event(event) => { if (root.is-video) { accept - } else if (event.delta-y == 0) { - accept } else if (event.delta-y > 0) { root.backlog(); is_backlog = true; @@ -288,11 +286,7 @@ export component StoryView { y: 0; text: "自动"; is-on: is-auto; - clicked => { - is-auto = !is-auto; - is-skip = false; - root.auto-play(true); - } + clicked => { root.auto-play(is-auto); } } CustomRoundButton { @@ -302,11 +296,7 @@ export component StoryView { y: 0; text: "快进"; is-on: is-skip; - clicked => { - is-skip = !is-skip; - is-auto = false; - root.skip-play(true); - } + clicked => { root.skip-play(is-skip); } } CustomRoundButton { @@ -317,8 +307,6 @@ export component StoryView { text: "履历"; clicked => { is-backlog = !is-backlog; - is-auto = false; - is-skip = false; root.backlog(); } } diff --git a/ui/main_window.slint b/ui/main_window.slint index ce450a0..56bc4ac 100644 --- a/ui/main_window.slint +++ b/ui/main_window.slint @@ -182,19 +182,9 @@ export component MainWindow inherits Window { is-auto <=> root.is-auto; is-skip <=> root.is-skip; - auto-play(b) => { - root.auto-play(b); - root.skip-play(false); - } - skip-play(b) => { - root.skip-play(b); - root.auto-play(false); - } - backlog => { - root.auto-play(false); - root.skip-play(false); - root.backlog(); - } + auto-play(b) => { root.auto-play(b); } + skip-play(b) => { root.skip-play(b); } + backlog => { root.backlog(); } 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) } @@ -203,12 +193,15 @@ export component MainWindow inherits Window { replay-voice => { root.replay-voice(); } save-game => { esc_story(3); + root.normal-play(); } load-game => { esc_story(4); + root.normal-play(); } settings => { esc_story(1); + root.normal-play(); } } @@ -243,6 +236,7 @@ export component MainWindow inherits Window { callback auto-play(bool); callback skip-play(bool); + callback normal-play(); callback clicked(); callback toggle-fullscreen(); callback volume-changed(); From c5b560b6e7177a8edafdfc98b6b9b949373fe0dc Mon Sep 17 00:00:00 2001 From: nanase <1925792291@qq.com> Date: Sat, 25 Jul 2026 17:42:32 +0800 Subject: [PATCH 3/8] 1 --- Cargo.toml | 2 +- savedata/data.toml | 8 ++--- src/config/cg.rs | 6 ++-- src/data/cg.rs | 16 ---------- src/data/mod.rs | 62 +++++++++++++++++++++++++++++++++++++-- src/data/read.rs | 0 src/data/user.rs | 52 -------------------------------- src/executors/executor.rs | 56 ++++++++++++++++++++++++++++------- src/executors/mod.rs | 3 +- src/script.rs | 8 +++++ 10 files changed, 121 insertions(+), 92 deletions(-) delete mode 100644 src/data/cg.rs delete mode 100644 src/data/read.rs delete mode 100644 src/data/user.rs diff --git a/Cargo.toml b/Cargo.toml index 7e674e5..5f7157c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ build = "src/ui/build.rs" [dependencies] slint = "1.17.1" rodio = "0.17" -tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "macros", "sync", "time"] } +tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "macros", "sync", "time", "fs"] } serde = { version = "1.0.219", features = ["derive"] } toml = "0.9.5" lazy_static = "1.5.0" diff --git a/savedata/data.toml b/savedata/data.toml index 8b77e10..08fa34d 100644 --- a/savedata/data.toml +++ b/savedata/data.toml @@ -1,8 +1,6 @@ -[cg] cg = [1022] [read] -read = [ - {name = "ky01", index = 4}, - {name = "ky02", index = 6}, -] +ky02 = 0 +ky03 = 2 +ky01 = 3 diff --git a/src/config/cg.rs b/src/config/cg.rs index ba2325b..1cdeb95 100644 --- a/src/config/cg.rs +++ b/src/config/cg.rs @@ -3,7 +3,7 @@ use crate::error::{EngineError, ExecutorError}; use crate::ui::initialize::ExItem; use serde::{Deserialize, Serialize}; use slint::{Image, ModelRc, VecModel}; -use std::{cell::RefCell, path::Path, rc::Rc}; +use std::{path::Path, rc::Rc}; use std::{collections::HashMap, fs}; lazy_static::lazy_static! { @@ -62,9 +62,7 @@ fn load_cg() -> CgConfig { } } -pub(crate) fn get_cg(cg: Rc>>) -> Result>, EngineError> { - let cg_map = cg.borrow(); - +pub(crate) fn get_cg(cg_map: &[u64]) -> Result>, EngineError> { let mut ex_items: Vec> = Vec::with_capacity(10); let mut i = 1; let mut ex_page: Vec = Vec::new(); diff --git a/src/data/cg.rs b/src/data/cg.rs deleted file mode 100644 index 52582b7..0000000 --- a/src/data/cg.rs +++ /dev/null @@ -1,16 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct CgMap { - cg: Vec, -} - -impl CgMap { - pub(crate) fn new(cg: Vec) -> Self { - Self { cg } - } - - pub(crate) fn cg(&self) -> Vec { - self.cg.clone() - } -} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index ee954dd..a4e43a1 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,4 +1,60 @@ -pub(crate) mod user; +use crate::config::{cg::CG_CONFIG, ENGINE_CONFIG}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use tokio::sync::watch; -pub(crate) mod cg; -pub(crate) mod read; \ No newline at end of file +#[derive(Debug, Clone, Deserialize, Serialize)] +pub(crate) struct UserData { + #[serde(default)] + pub(crate) cg: Vec, + #[serde(default)] + pub(crate) read: HashMap, +} + +impl Default for UserData { + fn default() -> Self { + let num = CG_CONFIG.length() / 64 + 1; + Self { + cg: vec![0; num], + read: HashMap::new(), + } + } +} + +impl UserData { + pub(crate) fn load() -> Self { + let path = format!("{}/data.toml", ENGINE_CONFIG.save_path()); + let mut data = fs::read_to_string(&path) + .ok() + .and_then(|content| toml::from_str::(&content).ok()) + .unwrap_or_default(); + // 防御:cg 长度不足时补齐,避免解锁时越界 + let num = CG_CONFIG.length() / 64 + 1; + if data.cg.len() < num { + data.cg.resize(num, 0); + } + data + } +} + +pub(crate) fn spawn_save_task() -> watch::Sender> { + let (tx, mut rx) = watch::channel::>(None); + tokio::spawn(async move { + while rx.changed().await.is_ok() { + let snapshot = rx.borrow_and_update().clone(); + if let Some(data) = snapshot { + let path = format!("{}/data.toml", ENGINE_CONFIG.save_path()); + match toml::to_string(&data) { + Ok(content) => { + if let Err(e) = tokio::fs::write(&path, content).await { + eprintln!("save user data failed: {e}"); + } + } + Err(e) => eprintln!("serialize user data failed: {e}"), + } + } + } + }); + tx +} diff --git a/src/data/read.rs b/src/data/read.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/data/user.rs b/src/data/user.rs deleted file mode 100644 index 28ec96e..0000000 --- a/src/data/user.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::config::{cg::CG_CONFIG, ENGINE_CONFIG}; -use crate::error::{EngineError, SaveError}; -use crate::executors::executor::Executor; -use serde::{Deserialize, Serialize}; -use std::cell::RefCell; -use std::fs; -use std::rc::Rc; -use crate::data::cg::CgMap; - -lazy_static::lazy_static! { - pub(crate) static ref USER_DATA: UserData = load_user_data(); -} - -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct UserData { - cg: CgMap, -} - -impl UserData { - pub(crate) fn cg(&self) -> Vec { - self.cg.cg() - } -} - -impl Executor { - pub(crate) fn load_cg(&mut self) { - self.set_cg(USER_DATA.cg()); - } -} - -fn load_user_data() -> UserData { - if let Ok(content) = fs::read_to_string(format!("{}/data.toml", ENGINE_CONFIG.save_path())) { - toml::from_str(&content).unwrap() - } else { - let num = CG_CONFIG.length() / 64 + 1; - UserData { - cg: CgMap::new(vec![0; num]), - } - } -} - -pub(crate) fn save_user_data(cg: Rc>>) -> Result<(), EngineError> { - let cg = cg.borrow(); - let path = format!("{}/data.toml", ENGINE_CONFIG.save_path()); - let content = toml::to_string(&UserData { - cg: CgMap::new(cg.clone()), - }) - .map_err(SaveError::from)?; - fs::write(&path, content).map_err(|e| SaveError::Write { path, source: e })?; - - Ok(()) -} diff --git a/src/executors/executor.rs b/src/executors/executor.rs index 8128b5a..844188b 100644 --- a/src/executors/executor.rs +++ b/src/executors/executor.rs @@ -3,7 +3,7 @@ use crate::config::{ cg::CG_CONFIG, figure::FIGURE_CONFIG, save_load::SaveData, user::save_user_config, voice::VOICE_LENGTH, ENGINE_CONFIG, }; -use crate::data::user::save_user_data; +use crate::data::UserData; use crate::error::{EngineError, SaveError}; use crate::executors::{delay_executor::{DelayChannels, DelayTX}, text_executor::{DisplayText, TextTX}, Status}; use crate::media::{ @@ -23,6 +23,7 @@ use std::{ time::Duration, }; use tokio::sync::mpsc::Sender; +use tokio::sync::watch; pub(crate) enum Jump { Label(Label), @@ -70,7 +71,8 @@ fn parse_position(position: &str, distance: &str) -> (f32, f32, f32) { pub(crate) struct Executor { script: Rc>, media_player: Rc>, - cg: Rc>>, + user_data: Rc>, + save_tx: Option>>, weak: Weak, text: Arc>, choose_lock: Rc>, @@ -85,7 +87,12 @@ pub(crate) struct Executor { impl Executor { pub(crate) fn new(weak: Weak) -> Result { - let script = Parser::load("ky01")?; + let mut script = Parser::load("ky01")?; + + // 加载用户存档,并把启动剧本的已读进度注入 Script + let user_data = UserData::load(); + let read_block = user_data.read.get(script.name()).copied().unwrap_or(0); + script.set_read_block(read_block); let figure_items = Rc::new(VecModel::::default()); if let Some(window) = weak.upgrade() { @@ -95,7 +102,8 @@ impl Executor { Ok(Executor { script: Rc::new(RefCell::new(script)), media_player: Rc::new(RefCell::new(MediaPlayer::new()?)), - cg: Rc::new(RefCell::new(Vec::new())), + user_data: Rc::new(RefCell::new(user_data)), + save_tx: None, weak, text: Arc::new(RwLock::new(DisplayText::new())), choose_lock: Rc::new(RefCell::new(false)), @@ -113,8 +121,20 @@ impl Executor { self.weak.clone() } - pub(crate) fn set_cg(&mut self, cg: Vec) { - *self.cg.borrow_mut() = cg; + pub(crate) fn set_save_tx(&mut self, save_tx: watch::Sender>) { + self.save_tx = Some(save_tx); + } + + /// 读取某剧本已记录的已读进度(不存在则为 0) + fn read_of(&self, name: &str) -> usize { + self.user_data.borrow().read.get(name).copied().unwrap_or(0) + } + + /// 向异步保存 task 推送最新快照(非阻塞) + fn request_save(&self) { + if let Some(tx) = &self.save_tx { + let _ = tx.send(Some(self.user_data.borrow().clone())); + } } pub(crate) fn set_text_tx(&mut self, text_tx: Sender>>) { @@ -150,8 +170,8 @@ impl Executor { } pub(crate) fn unlock(&mut self, index: usize) { - let mut cg = self.cg.borrow_mut(); - cg[index / 64] |= 1u64 << index; + let mut ud = self.user_data.borrow_mut(); + ud.cg[index / 64] |= 1u64 << index; } pub(crate) fn execute_backlog(&mut self) -> Result<(), EngineError> { @@ -244,7 +264,7 @@ impl Executor { } pub(crate) fn execute_get_ex(&self) -> Result<(), EngineError> { - let ex_items = get_cg(self.cg.clone())?; + let ex_items = get_cg(&self.user_data.borrow().cg)?; if let Some(window) = self.weak.upgrade() { window.set_ex_items(ex_items); @@ -321,6 +341,7 @@ impl Executor { if name != script.name() { let mut scr = Parser::load(&name)?; scr.set_backlog(backlog); + scr.set_read_block(self.read_of(&name)); *script = scr; } script.find_label(&label).copied() @@ -329,6 +350,7 @@ impl Executor { if name != script.name() { let mut scr = Parser::load(&name)?; scr.set_backlog(backlog); + scr.set_read_block(self.read_of(&name)); *script = scr; } Some(index as usize) @@ -460,6 +482,20 @@ impl Executor { } }; + // 同步当前剧本的已读进度到 user_data,并异步实时保存 + { + let (name, rb) = { + let scr = self.script.borrow(); + (scr.name().to_string(), scr.read_block()) + }; + let mut ud = self.user_data.borrow_mut(); + let entry = ud.read.entry(name).or_insert(0); + if rb > *entry { + *entry = rb; + } + } + self.request_save(); + if is_wait { duration += delay; } @@ -637,7 +673,7 @@ impl Executor { let path = if *is_cg { if let Some((index, _)) = CG_CONFIG.find_by_name(name) { self.unlock(*index); - save_user_data(self.cg.clone())?; + self.request_save(); } ENGINE_CONFIG.cg_path() } else { diff --git a/src/executors/mod.rs b/src/executors/mod.rs index 904cd66..4d944dc 100644 --- a/src/executors/mod.rs +++ b/src/executors/mod.rs @@ -1,3 +1,4 @@ +use crate::data::spawn_save_task; use crate::error::EngineError; use crate::executors::{ auto_executor::AutoExecutor, delay_executor::DelayExecutor, executor::Executor, @@ -65,13 +66,13 @@ pub(crate) fn load_data(executor: &mut Executor) -> Result usize { + self.read_block + } + pub(crate) fn next_command(&mut self) -> Option<&Commands> { let command = self.commands.get(self.current_block); if self.read_block <= self.current_block { From 930cf826fd663fdf806ffec2fc8fc11c081f1a26 Mon Sep 17 00:00:00 2001 From: nanase <1925792291@qq.com> Date: Sat, 25 Jul 2026 17:51:51 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- savedata/data.toml | 2 +- source/cg/length.toml | 21 ++++++----- source/figure/rar/rar.toml | 38 +++++++++----------- source/figure/rir/rir.toml | 68 ++++++++++++++++-------------------- source/voice/rar/length.toml | 7 ++-- source/voice/rir/length.toml | 19 +++++----- src/config/cg.rs | 33 ++++++++--------- src/config/figure.rs | 49 ++++---------------------- src/config/voice.rs | 23 +++--------- 9 files changed, 96 insertions(+), 164 deletions(-) diff --git a/savedata/data.toml b/savedata/data.toml index 08fa34d..530e4df 100644 --- a/savedata/data.toml +++ b/savedata/data.toml @@ -1,6 +1,6 @@ cg = [1022] [read] -ky02 = 0 ky03 = 2 ky01 = 3 +ky02 = 0 diff --git a/source/cg/length.toml b/source/cg/length.toml index be29d59..845beea 100644 --- a/source/cg/length.toml +++ b/source/cg/length.toml @@ -1,11 +1,10 @@ -cast = [ - {name = "ev_rir_03_01", index = 1, length = 3}, - {name = "ev_rir_03_02", index = 2, length = 1}, - {name = "ev_rir_03_03", index = 3, length = 1}, - {name = "ev_rir_04_01", index = 4, length = 5}, - {name = "ev_rir_04_02", index = 5, length = 1}, - {name = "ev_rir_04_03", index = 6, length = 1}, - {name = "ev_rir_04_04", index = 7, length = 1}, - {name = "ev_rir_04_05", index = 8, length = 1}, - {name = "ev_rir_06_01", index = 9, length = 1}, -] \ No newline at end of file +[cast] +ev_rir_03_01 = { index = 1, length = 3 } +ev_rir_03_02 = { index = 2, length = 1 } +ev_rir_03_03 = { index = 3, length = 1 } +ev_rir_04_01 = { index = 4, length = 5 } +ev_rir_04_02 = { index = 5, length = 1 } +ev_rir_04_03 = { index = 6, length = 1 } +ev_rir_04_04 = { index = 7, length = 1 } +ev_rir_04_05 = { index = 8, length = 1 } +ev_rir_06_01 = { index = 9, length = 1 } diff --git a/source/figure/rar/rar.toml b/source/figure/rar/rar.toml index f7deb60..29d4553 100755 --- a/source/figure/rar/rar.toml +++ b/source/figure/rar/rar.toml @@ -1,26 +1,22 @@ [body] -cast = [ - {name = "rar_noa0200", rate = 0.412}, - {name = "rar_z1a0200", rate = 0.414}, -] +rar_noa0200 = 0.412 +rar_z1a0200 = 0.414 [face] -cast = [ - {name = "noa0002", x = 0.361, y = 0.0825}, - {name = "noa0015", x = 0.357, y = 0.0907}, - {name = "noa0039", x = 0.363, y = 0.0907}, - {name = "noa0041", x = 0.363, y = 0.0874}, - {name = "noa0052", x = 0.363, y = 0.0874}, - {name = "noa0053", x = 0.363, y = 0.0874}, - {name = "noa0054", x = 0.369, y = 0.0899}, - {name = "z1a0002", x = 0.356, y = 0.0823}, - {name = "z1a0015", x = 0.352, y = 0.0900}, - {name = "z1a0039", x = 0.355, y = 0.0905}, - {name = "z1a0041", x = 0.355, y = 0.0868}, - {name = "z1a0052", x = 0.355, y = 0.0873}, - {name = "z1a0053", x = 0.355, y = 0.0873}, - {name = "z1a0054", x = 0.363, y = 0.0895}, -] +noa0002 = [0.361, 0.0825] +noa0015 = [0.357, 0.0907] +noa0039 = [0.363, 0.0907] +noa0041 = [0.363, 0.0874] +noa0052 = [0.363, 0.0874] +noa0053 = [0.363, 0.0874] +noa0054 = [0.369, 0.0899] +z1a0002 = [0.356, 0.0823] +z1a0015 = [0.352, 0.0900] +z1a0039 = [0.355, 0.0905] +z1a0041 = [0.355, 0.0868] +z1a0052 = [0.355, 0.0873] +z1a0053 = [0.355, 0.0873] +z1a0054 = [0.363, 0.0895] [offset] -offset = 0.0298 \ No newline at end of file +offset = 0.0298 diff --git a/source/figure/rir/rir.toml b/source/figure/rir/rir.toml index 60df34d..e462ada 100644 --- a/source/figure/rir/rir.toml +++ b/source/figure/rir/rir.toml @@ -1,42 +1,36 @@ [body] -cast = [ - {name = "rir_z1a0200", rate = 0.363}, - {name = "rir_z1b0200", rate = 0.389}, - {name = "rir_z1b0210", rate = 0.389}, - {name = "rir_noa0200", rate = 0.363}, - {name = "rir_nob0200", rate = 0.386}, -] +rir_z1a0200 = 0.363 +rir_z1b0200 = 0.389 +rir_z1b0210 = 0.389 +rir_noa0200 = 0.363 +rir_nob0200 = 0.386 [face] -cast = [ - {name = "z1a0050", x = 0.343, y = 0.0507}, - {name = "z1a0043", x = 0.337, y = 0.0808}, - {name = "z1a0049", x = 0.339, y = 0.0838}, - {name = "z1b0058", x = 0.332, y = 0.0728}, - {name = "z1b0059", x = 0.327, y = 0.0757}, - {name = "z1b0050", x = 0.337, y = 0.0530}, - {name = "z1a0002", x = 0.328, y = 0.0784}, - {name = "z1a0009", x = 0.326, y = 0.0735}, - {name = "z1a0038", x = 0.334, y = 0.0814}, - {name = "z1a0039", x = 0.337, y = 0.0804}, - {name = "z1a0050", x = 0.343, y = 0.0507}, - {name = "z1a0052", x = 0.330, y = 0.0764}, - {name = "z1b0050", x = 0.337, y = 0.0530}, - {name = "z1b0008", x = 0.318, y = 0.0655}, - {name = "z1b0010", x = 0.318, y = 0.0664}, - {name = "z1b0012", x = 0.332, y = 0.0758}, - {name = "z1b0031", x = 0.332, y = 0.0817}, - {name = "noa0002", x = 0.328, y = 0.0793}, - {name = "noa0009", x = 0.325, y = 0.0733}, - {name = "noa0038", x = 0.332, y = 0.0819}, - {name = "noa0039", x = 0.335, y = 0.0810}, - {name = "noa0050", x = 0.343, y = 0.0507}, - {name = "noa0052", x = 0.330, y = 0.0776}, - {name = "nob0050", x = 0.337, y = 0.0530}, - {name = "nob0008", x = 0.319, y = 0.0655}, - {name = "nob0010", x = 0.319, y = 0.0655}, - {name = "nob0031", x = 0.335, y = 0.0810}, -] +z1a0050 = [0.343, 0.0507] +z1a0043 = [0.337, 0.0808] +z1a0049 = [0.339, 0.0838] +z1b0058 = [0.332, 0.0728] +z1b0059 = [0.327, 0.0757] +z1b0050 = [0.337, 0.0530] +z1a0002 = [0.328, 0.0784] +z1a0009 = [0.326, 0.0735] +z1a0038 = [0.334, 0.0814] +z1a0039 = [0.337, 0.0804] +z1a0052 = [0.330, 0.0764] +z1b0008 = [0.318, 0.0655] +z1b0010 = [0.318, 0.0664] +z1b0012 = [0.332, 0.0758] +z1b0031 = [0.332, 0.0817] +noa0002 = [0.328, 0.0793] +noa0009 = [0.325, 0.0733] +noa0038 = [0.332, 0.0819] +noa0039 = [0.335, 0.0810] +noa0050 = [0.343, 0.0507] +noa0052 = [0.330, 0.0776] +nob0050 = [0.337, 0.0530] +nob0008 = [0.319, 0.0655] +nob0010 = [0.319, 0.0655] +nob0031 = [0.335, 0.0810] [offset] -offset = 0.0 \ No newline at end of file +offset = 0.0 diff --git a/source/voice/rar/length.toml b/source/voice/rar/length.toml index b46995f..a916e1b 100644 --- a/source/voice/rar/length.toml +++ b/source/voice/rar/length.toml @@ -1,4 +1,3 @@ -cast = [ - {name = "fem_rar_50055", length = 4}, - {name = "fem_rar_50056", length = 6}, -] \ No newline at end of file +[cast] +fem_rar_50055 = 4 +fem_rar_50056 = 6 diff --git a/source/voice/rir/length.toml b/source/voice/rir/length.toml index 4c004d1..5c10e10 100644 --- a/source/voice/rir/length.toml +++ b/source/voice/rir/length.toml @@ -1,10 +1,9 @@ -cast = [ - {name = "fem_rir_50236", length = 2}, - {name = "fem_rir_50237", length = 10}, - {name = "fem_rir_50238", length = 10}, - {name = "fem_rir_50239", length = 3}, - {name = "fem_rir_50240", length = 6}, - {name = "fem_rir_50520", length = 11}, - {name = "fem_rir_50521", length = 11}, - {name = "fem_rir_50522", length = 9}, -] \ No newline at end of file +[cast] +fem_rir_50236 = 2 +fem_rir_50237 = 10 +fem_rir_50238 = 10 +fem_rir_50239 = 3 +fem_rir_50240 = 6 +fem_rir_50520 = 11 +fem_rir_50521 = 11 +fem_rir_50522 = 9 diff --git a/src/config/cg.rs b/src/config/cg.rs index 1cdeb95..f66f694 100644 --- a/src/config/cg.rs +++ b/src/config/cg.rs @@ -10,16 +10,15 @@ lazy_static::lazy_static! { pub(crate) static ref CG_CONFIG: CgConfig = load_cg(); } -#[derive(Debug, Deserialize, Serialize, Clone)] -struct Length { - name: String, +#[derive(Debug, Deserialize)] +struct CgEntry { index: usize, length: u64, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize)] struct LengthWrapper { - cast: Vec, + cast: HashMap, } #[derive(Debug, Deserialize, Serialize)] @@ -44,20 +43,18 @@ impl CgConfig { } fn load_cg() -> CgConfig { - let content = fs::read_to_string(format!("{}length.toml", ENGINE_CONFIG.cg_path(),)).unwrap(); - let name_item: LengthWrapper = toml::from_str(&content).unwrap(); - let index_item = name_item.cast.clone(); - let length = index_item.len(); + let content = fs::read_to_string(format!("{}length.toml", ENGINE_CONFIG.cg_path())).unwrap(); + let item: LengthWrapper = toml::from_str(&content).unwrap(); + let length = item.cast.len(); + let mut cg_by_name = HashMap::new(); + let mut cg_by_id = HashMap::new(); + for (name, entry) in item.cast { + cg_by_name.insert(name.clone(), (entry.index, entry.length)); + cg_by_id.insert(entry.index, (name, entry.length)); + } CgConfig { - cg_by_name: name_item - .cast - .into_iter() - .map(|length| (length.name, (length.index, length.length))) - .collect(), - cg_by_id: index_item - .into_iter() - .map(|length| (length.index, (length.name, length.length))) - .collect(), + cg_by_name, + cg_by_id, length, } } diff --git a/src/config/figure.rs b/src/config/figure.rs index 1560c2a..8c3dc96 100644 --- a/src/config/figure.rs +++ b/src/config/figure.rs @@ -6,38 +6,15 @@ lazy_static::lazy_static! { pub(crate) static ref FIGURE_CONFIG: FigureConfig = load_figure(); } -#[derive(Debug, Deserialize, Serialize)] -struct Face { - name: String, - x: f32, - y: f32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct FaceWrapper { - cast: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -struct Body { - name: String, - rate: f32, -} - -#[derive(Debug, Deserialize, Serialize)] -struct BodyWrapper { - cast: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize)] struct Offset { offset: f32, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize)] struct FigureRead { - body: BodyWrapper, - face: FaceWrapper, + body: HashMap, + face: HashMap, offset: Offset, } @@ -77,22 +54,8 @@ fn load_figure() -> FigureConfig { )) .unwrap(); let item: FigureRead = toml::from_str(&content).unwrap(); - face_list.insert( - char.to_string(), - item.face - .cast - .into_iter() - .map(|face| (face.name, (face.x, face.y))) - .collect(), - ); - body_list.insert( - char.to_string(), - item.body - .cast - .into_iter() - .map(|body| (body.name, body.rate)) - .collect(), - ); + face_list.insert(char.to_string(), item.face); + body_list.insert(char.to_string(), item.body); offset_list.insert(char.to_string(), item.offset.offset); } diff --git a/src/config/voice.rs b/src/config/voice.rs index 197ac59..52df3b8 100644 --- a/src/config/voice.rs +++ b/src/config/voice.rs @@ -1,5 +1,5 @@ use crate::config::ENGINE_CONFIG; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs}; use tokio::time::Duration; @@ -7,16 +7,9 @@ lazy_static::lazy_static! { pub(crate) static ref VOICE_LENGTH: VoiceLength = load_voice(); } -#[derive(Debug, Deserialize, Serialize)] -struct Length { - name: String, - #[serde(deserialize_with = "deserialize_duration_from_secs")] - length: Duration, -} - -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize)] struct LengthWrapper { - cast: Vec, + cast: HashMap, } #[derive(Debug, Deserialize, Serialize)] @@ -44,18 +37,10 @@ fn load_voice() -> VoiceLength { char.to_string(), item.cast .into_iter() - .map(|length| (length.name, length.length)) + .map(|(name, secs)| (name, Duration::from_secs(secs))) .collect(), ); } VoiceLength { voice_length } } - -pub(crate) fn deserialize_duration_from_secs<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let seconds = u64::deserialize(deserializer)?; - Ok(Duration::from_secs(seconds)) -} From 7a2eeca485cc4533d3ac51bf9002d3ef54022e6e Mon Sep 17 00:00:00 2001 From: nanase <1925792291@qq.com> Date: Sat, 25 Jul 2026 18:30:16 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E5=B7=B2=E8=AF=BB=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=BF=AB=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- savedata/data.toml | 4 ++-- src/data/mod.rs | 1 - src/executors/executor.rs | 10 +++++++++- src/executors/mod.rs | 3 ++- src/executors/skip_executor.rs | 19 ++++++++++++------- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/savedata/data.toml b/savedata/data.toml index 530e4df..565a724 100644 --- a/savedata/data.toml +++ b/savedata/data.toml @@ -1,6 +1,6 @@ cg = [1022] [read] -ky03 = 2 ky01 = 3 -ky02 = 0 +ky02 = 8 +ky03 = 2 diff --git a/src/data/mod.rs b/src/data/mod.rs index a4e43a1..86e2cff 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -29,7 +29,6 @@ impl UserData { .ok() .and_then(|content| toml::from_str::(&content).ok()) .unwrap_or_default(); - // 防御:cg 长度不足时补齐,避免解锁时越界 let num = CG_CONFIG.length() / 64 + 1; if data.cg.len() < num { data.cg.resize(num, 0); diff --git a/src/executors/executor.rs b/src/executors/executor.rs index 844188b..294ee55 100644 --- a/src/executors/executor.rs +++ b/src/executors/executor.rs @@ -89,7 +89,6 @@ impl Executor { pub(crate) fn new(weak: Weak) -> Result { let mut script = Parser::load("ky01")?; - // 加载用户存档,并把启动剧本的已读进度注入 Script let user_data = UserData::load(); let read_block = user_data.read.get(script.name()).copied().unwrap_or(0); script.set_read_block(read_block); @@ -169,6 +168,11 @@ impl Executor { None } + pub(crate) fn can_skip(&self) -> bool { + let scr = self.script.borrow(); + scr.read_block() > scr.index() + } + pub(crate) fn unlock(&mut self, index: usize) { let mut ud = self.user_data.borrow_mut(); ud.cg[index / 64] |= 1u64 << index; @@ -386,6 +390,10 @@ impl Executor { auto_tx.try_send(())?; } Status::Skip => { + if !self.can_skip() { + return Ok(()) + } + if symbol { window.set_is_skip(false); } else { diff --git a/src/executors/mod.rs b/src/executors/mod.rs index 4d944dc..fb3bde4 100644 --- a/src/executors/mod.rs +++ b/src/executors/mod.rs @@ -63,8 +63,9 @@ pub(crate) fn load_data(executor: &mut Executor) -> Result Date: Sat, 25 Jul 2026 19:16:57 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/components/common/switch_toggle.slint | 32 ++++++++++++++++++++ ui/components/config/screen.slint | 34 +++++----------------- ui/components/config/system.slint | 37 +++++------------------- ui/components/config/text.slint | 28 ++++++------------ 4 files changed, 57 insertions(+), 74 deletions(-) create mode 100644 ui/components/common/switch_toggle.slint diff --git a/ui/components/common/switch_toggle.slint b/ui/components/common/switch_toggle.slint new file mode 100644 index 0000000..8911ef7 --- /dev/null +++ b/ui/components/common/switch_toggle.slint @@ -0,0 +1,32 @@ +import { Switch } from "std-widgets.slint"; +import { Colors } from "../../styles/colors.slint"; + +// 文本 + 开关:label 在左,Switch 紧随其后 +export component SwitchToggle { + in property label; + in-out property checked; + in property texts; + + callback toggled(); + + Rectangle { + Text { + x: 0; + y: 0; + width: parent.width * 0.3; + text: label; + font-size: parent.height * 0.8; + color: Colors.text-light; + vertical-alignment: center; + } + + Switch { + x: parent.height * 1.2 * texts; + y: 0; + checked <=> root.checked; + toggled => { + root.toggled(); + } + } + } +} diff --git a/ui/components/config/screen.slint b/ui/components/config/screen.slint index 49a1a4d..64421ab 100644 --- a/ui/components/config/screen.slint +++ b/ui/components/config/screen.slint @@ -1,5 +1,4 @@ -import { Switch } from "std-widgets.slint"; -import { Colors } from "../../styles/colors.slint"; +import { SwitchToggle } from "../common/switch_toggle.slint"; export component ScreenConfig { in property is-fullscreen; @@ -7,33 +6,16 @@ export component ScreenConfig { callback toggle-fullscreen(); Rectangle { - Rectangle { + SwitchToggle { x: parent.width * 0.02; y: 0; + width: parent.width * 0.35; height: parent.height * 0.03; - - Text { - x: 0; - y: 0; - text: "全屏开关"; - font-size: parent.height * 0.8; - color: Colors.text-light; - vertical-alignment: center; - width: parent.width * 0.12; - } - - Rectangle { - x: parent.width * 0.08; - y: 0; - width: parent.width * 0.032; - height: parent.height; - - Switch { - checked: root.is-fullscreen; - width: parent.width; - height: parent.height; - toggled => { root.toggle-fullscreen(); } - } + label: "全屏开关"; + texts: 6; + checked: root.is-fullscreen; + toggled => { + root.toggle-fullscreen(); } } } diff --git a/ui/components/config/system.slint b/ui/components/config/system.slint index 0727e52..2419474 100644 --- a/ui/components/config/system.slint +++ b/ui/components/config/system.slint @@ -1,51 +1,30 @@ -import { Switch } from "std-widgets.slint"; import { VolumeSlider } from "../common/volume_slider.slint"; -import { Colors } from "../../styles/colors.slint"; +import { SwitchToggle } from "../common/switch_toggle.slint"; export component SystemConfig { in-out property is-wait; in-out property delay; Rectangle { - Rectangle { + SwitchToggle { x: parent.width * 0.02; y: 0; + width: parent.width * 0.35; height: parent.height * 0.035; - - Text { - x: 0; - y: 0; - text: "自动等待语音"; - font-size: parent.height * 0.8; - color: Colors.text-light; - vertical-alignment: center; - width: parent.width * 0.12; - } - - Rectangle { - x: parent.height * 1.2 * 5.5; - y: 0; - width: parent.width * 0.032; - height: parent.height; - - Switch { - checked: root.is-wait; - width: parent.width; - height: parent.height; - toggled => { root.is-wait = !root.is-wait } - } - } + label: "自动等待语音"; + texts: 6; + checked <=> root.is-wait; } VolumeSlider { - y: parent.height * 0.05; + y: parent.height * 0.08; x: parent.width * 0.02; width: parent.width * 0.35; height: parent.height * 0.035; max: 5.0; min: 1.0; label: "自动等待时长"; - texts: 5.5; + texts: 6; volume: root.delay; volume-changed(value) => { root.delay = value; diff --git a/ui/components/config/text.slint b/ui/components/config/text.slint index d47c146..4f55a46 100644 --- a/ui/components/config/text.slint +++ b/ui/components/config/text.slint @@ -2,7 +2,7 @@ import { CustomButton } from "../common/button.slint"; import { VolumeSlider } from "../common/volume_slider.slint"; import { FontDrawer } from "../common/drawer.slint"; import { Colors } from "../../styles/colors.slint"; -import { Switch } from "std-widgets.slint"; +import { SwitchToggle } from "../common/switch_toggle.slint"; export component TextConfig { in-out property text-speed; @@ -45,33 +45,23 @@ export component TextConfig { } } - Text { + SwitchToggle { y: parent.height * 0.16; x: parent.width * 0.02; width: parent.width * 0.35; height: parent.height * 0.035; - text: "文本加粗"; - font-size: parent.height * 0.035 * 0.8; - color: Colors.text-light; - } - - Switch { - y: parent.height * 0.16; - x: parent.height * 0.252 + parent.width * 0.02; + label: "文本加粗"; + texts: 6; checked <=> root.is-bold; } - Text { + SwitchToggle { y: parent.height * 0.24; x: parent.width * 0.02; - text: "文本阴影"; - font-size: parent.height * 0.035 * 0.8; - color: Colors.text-light; - } - - Switch { - y: parent.height * 0.24; - x: parent.height * 0.252 + parent.width * 0.02; + width: parent.width * 0.35; + height: parent.height * 0.035; + label: "文本阴影"; + texts: 6; checked <=> root.show-shadow; } From b7b6a5bab5b1bf561e4d30d23d00aaf427d46ee3 Mon Sep 17 00:00:00 2001 From: nanase <1925792291@qq.com> Date: Sat, 25 Jul 2026 20:33:11 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E5=B7=B2=E8=AF=BB=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=8F=98=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- savedata/data.toml | 4 ++-- src/executors/executor.rs | 6 ++++++ src/executors/skip_executor.rs | 2 +- ui/components/config/system.slint | 11 +++++++++++ ui/components/main_config.slint | 2 ++ ui/components/story.slint | 7 ++++--- ui/main_window.slint | 6 ++++-- ui/styles/colors.slint | 1 + 8 files changed, 31 insertions(+), 8 deletions(-) diff --git a/savedata/data.toml b/savedata/data.toml index 565a724..e108565 100644 --- a/savedata/data.toml +++ b/savedata/data.toml @@ -1,6 +1,6 @@ cg = [1022] [read] +ky02 = 11 ky01 = 3 -ky02 = 8 -ky03 = 2 +ky03 = 3 diff --git a/src/executors/executor.rs b/src/executors/executor.rs index 294ee55..9723c03 100644 --- a/src/executors/executor.rs +++ b/src/executors/executor.rs @@ -170,6 +170,11 @@ impl Executor { pub(crate) fn can_skip(&self) -> bool { let scr = self.script.borrow(); + if let Some(window) = self.weak.upgrade() { + if window.get_skip_conf() { + return true; + } + } scr.read_block() > scr.index() } @@ -580,6 +585,7 @@ impl Executor { text.replace("{nns}", "").to_shared_string(), voice, ); + window.set_is_read(script.read_block() > script.index()); } window.set_speaker(SharedString::from(speaker)); { diff --git a/src/executors/skip_executor.rs b/src/executors/skip_executor.rs index 61a909a..9fec138 100644 --- a/src/executors/skip_executor.rs +++ b/src/executors/skip_executor.rs @@ -65,7 +65,7 @@ impl SkipExecutor { }) .expect("skip-play timer: no slint event loop"); } else { - executor.execute_status(Status::Skip, true).expect("skip-play execute_status failed"); + executor.execute_status(Status::Normal, false).expect("skip-play execute_status failed"); } } }, diff --git a/ui/components/config/system.slint b/ui/components/config/system.slint index 2419474..f9c22be 100644 --- a/ui/components/config/system.slint +++ b/ui/components/config/system.slint @@ -3,6 +3,7 @@ import { SwitchToggle } from "../common/switch_toggle.slint"; export component SystemConfig { in-out property is-wait; + in-out property skip-conf; in-out property delay; Rectangle { @@ -30,5 +31,15 @@ export component SystemConfig { root.delay = value; } } + + SwitchToggle { + x: parent.width * 0.02; + y: parent.height * 0.16; + width: parent.width * 0.35; + height: parent.height * 0.035; + label: "跳过未读文本"; + texts: 6; + checked <=> root.skip-conf; + } } } \ No newline at end of file diff --git a/ui/components/main_config.slint b/ui/components/main_config.slint index 66ec7da..cb9ff3f 100644 --- a/ui/components/main_config.slint +++ b/ui/components/main_config.slint @@ -18,6 +18,7 @@ export component SettingsView { in-out property voice-volume; in-out property <[CharacterVolume]> character_volumes: []; in-out property is-wait; + in-out property skip-conf; in-out property delay; in-out property text-speed; in-out property dialogue-opacity; @@ -134,6 +135,7 @@ export component SettingsView { width: parent.width; is-wait <=> root.is-wait; + skip-conf <=> root.skip-conf; delay <=> root.delay; } diff --git a/ui/components/story.slint b/ui/components/story.slint index 993d979..740abb3 100644 --- a/ui/components/story.slint +++ b/ui/components/story.slint @@ -40,6 +40,7 @@ export component StoryView { in property is-bold; in property show-shadow; in property dialogue-font; + in property is-read; in-out property is-video: false; in property video-frame; @@ -172,7 +173,7 @@ export component StoryView { font-size: parent.height * 2 / 15; font-weight: root.is-bold ? 700 : 400; font-family: root.dialogue-font; - color: Colors.text-light; + color: root.is-read ? Colors.read-color : Colors.text-light; wrap: word-wrap; width: parent.width * 31 / 32; } @@ -195,7 +196,7 @@ export component StoryView { font-size: parent.height * 2 / 15; font-weight: root.is-bold ? 700 : 400; font-family: root.dialogue-font; - color: Colors.text-light; + color: root.is-read ? Colors.read-color : Colors.text-light; wrap: word-wrap; width: parent.width * 31 / 32; } @@ -218,7 +219,7 @@ export component StoryView { font-size: parent.height * 2 / 15; font-weight: root.is-bold ? 700 : 400; font-family: root.dialogue-font; - color: Colors.text-light; + color: root.is-read ? Colors.read-color : Colors.text-light; wrap: word-wrap; width: parent.width * 31 / 32; } diff --git a/ui/main_window.slint b/ui/main_window.slint index 56bc4ac..0f7cb01 100644 --- a/ui/main_window.slint +++ b/ui/main_window.slint @@ -25,6 +25,7 @@ export component MainWindow inherits Window { in-out property is_backlog: false; in-out property is-auto; in-out property is-wait; + in-out property skip-conf; in-out property delay; in-out property text-speed; in-out property is-skip; @@ -36,6 +37,7 @@ export component MainWindow inherits Window { in-out property is-bold; in-out property show-shadow; in-out property dialogue-font; + in-out property is-read; in property <[string]> font-list; in-out property current-screen: 0; @@ -55,8 +57,6 @@ export component MainWindow inherits Window { out property container-height: container-size * 9 / 16; function esc_story(screen: int) { - root.auto-play(false); - root.skip-play(false); root.last-screen = 2; root.current-screen = screen; } @@ -139,6 +139,7 @@ export component MainWindow inherits Window { voice-volume <=> root.voice-volume; character_volumes <=> root.character_volumes; is-wait <=> root.is-wait; + skip-conf <=> root.skip-conf; delay <=> root.delay; dialogue-opacity <=> root.dialogue-opacity; is-bold <=> root.is-bold; @@ -172,6 +173,7 @@ export component MainWindow inherits Window { is-bold: root.is-bold; show-shadow: root.show-shadow; dialogue-font: root.dialogue-font; + is-read: root.is-read; is-video <=> root.is-video; video-frame: root.video-frame; diff --git a/ui/styles/colors.slint b/ui/styles/colors.slint index 49f052e..c04e492 100644 --- a/ui/styles/colors.slint +++ b/ui/styles/colors.slint @@ -8,4 +8,5 @@ export global Colors { out property slider-background: #ddd; out property border-color: #ccc; out property light-purple: #E0D0FF; + out property read-color: yellow; } \ No newline at end of file From 63dff8970443cd3d51a497b821700d3f7256fc4f Mon Sep 17 00:00:00 2001 From: nanase <1925792291@qq.com> Date: Sat, 25 Jul 2026 20:38:27 +0800 Subject: [PATCH 8/8] fmt --- src/config/cg.rs | 2 +- src/executors/executor.rs | 20 ++++++++++++++------ src/executors/mod.rs | 4 +++- src/executors/skip_executor.rs | 8 +++++--- src/main.rs | 2 +- src/ui/initialize.rs | 2 +- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/config/cg.rs b/src/config/cg.rs index f66f694..e9f347d 100644 --- a/src/config/cg.rs +++ b/src/config/cg.rs @@ -3,8 +3,8 @@ use crate::error::{EngineError, ExecutorError}; use crate::ui::initialize::ExItem; use serde::{Deserialize, Serialize}; use slint::{Image, ModelRc, VecModel}; -use std::{path::Path, rc::Rc}; use std::{collections::HashMap, fs}; +use std::{path::Path, rc::Rc}; lazy_static::lazy_static! { pub(crate) static ref CG_CONFIG: CgConfig = load_cg(); diff --git a/src/executors/executor.rs b/src/executors/executor.rs index 9723c03..0041374 100644 --- a/src/executors/executor.rs +++ b/src/executors/executor.rs @@ -1,11 +1,15 @@ use crate::config::cg::get_cg; use crate::config::{ - cg::CG_CONFIG, figure::FIGURE_CONFIG, save_load::SaveData, - user::save_user_config, voice::VOICE_LENGTH, ENGINE_CONFIG, + cg::CG_CONFIG, figure::FIGURE_CONFIG, save_load::SaveData, user::save_user_config, + voice::VOICE_LENGTH, ENGINE_CONFIG, }; use crate::data::UserData; use crate::error::{EngineError, SaveError}; -use crate::executors::{delay_executor::{DelayChannels, DelayTX}, text_executor::{DisplayText, TextTX}, Status}; +use crate::executors::{ + delay_executor::{DelayChannels, DelayTX}, + text_executor::{DisplayText, TextTX}, + Status, +}; use crate::media::{ player::{MediaPlayer, PreBgm, PreBgm::Play}, video_player::{VideoContext, VideoPlayer}, @@ -163,7 +167,7 @@ impl Executor { pub(crate) fn get_status_channel(&self) -> Option<(Sender<()>, Sender<()>)> { if let Some((auto_tx, skip_tx)) = self.status_channel.as_ref() { - return Some((auto_tx.clone(), skip_tx.clone())) + return Some((auto_tx.clone(), skip_tx.clone())); } None } @@ -373,7 +377,11 @@ impl Executor { Ok(()) } - pub(crate) fn execute_status(&mut self, status: Status, symbol: bool) -> Result<(), EngineError> { + pub(crate) fn execute_status( + &mut self, + status: Status, + symbol: bool, + ) -> Result<(), EngineError> { let (auto_tx, skip_tx) = self.get_status_channel().unwrap(); if let Some(window) = self.weak.upgrade() { @@ -396,7 +404,7 @@ impl Executor { } Status::Skip => { if !self.can_skip() { - return Ok(()) + return Ok(()); } if symbol { diff --git a/src/executors/mod.rs b/src/executors/mod.rs index fb3bde4..cf60755 100644 --- a/src/executors/mod.rs +++ b/src/executors/mod.rs @@ -63,7 +63,9 @@ pub(crate) fn load_data(executor: &mut Executor) -> Result Result<(), EngineError> { .expect("TODO: panic message"); } }); - + window.on_normal_play({ let mut executor = executor.clone(); move || {