From e5a72731ddd125cc97fdb9bd7fc4c147d7ea3d69 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Tue, 28 Feb 2023 10:03:56 +0100 Subject: [PATCH] refactor(dtmm): Split files into smaller modules --- crates/dtmm/src/{ => controller}/engine.rs | 119 ++-- crates/dtmm/src/{ => controller}/worker.rs | 2 +- crates/dtmm/src/main.rs | 29 +- crates/dtmm/src/state.rs | 510 ------------------ crates/dtmm/src/state/data.rs | 144 +++++ crates/dtmm/src/state/delegate.rs | 197 +++++++ crates/dtmm/src/state/lens.rs | 73 +++ crates/dtmm/src/state/mod.rs | 9 + crates/dtmm/src/state/util.rs | 31 ++ crates/dtmm/src/ui/mod.rs | 5 + crates/dtmm/src/{ => ui}/theme.rs | 0 crates/dtmm/src/{ => ui}/widget/container.rs | 0 crates/dtmm/src/{ => ui/widget}/controller.rs | 0 .../src/{ => ui}/widget/fill_container.rs | 0 crates/dtmm/src/{ => ui}/widget/mod.rs | 1 + .../dtmm/src/{ => ui}/widget/table_select.rs | 0 .../src/{main_window.rs => ui/window/main.rs} | 22 +- crates/dtmm/src/{util.rs => util/config.rs} | 0 crates/dtmm/src/{ => util}/log.rs | 0 19 files changed, 539 insertions(+), 603 deletions(-) rename crates/dtmm/src/{ => controller}/engine.rs (86%) rename crates/dtmm/src/{ => controller}/worker.rs (99%) delete mode 100644 crates/dtmm/src/state.rs create mode 100644 crates/dtmm/src/state/data.rs create mode 100644 crates/dtmm/src/state/delegate.rs create mode 100644 crates/dtmm/src/state/lens.rs create mode 100644 crates/dtmm/src/state/mod.rs create mode 100644 crates/dtmm/src/state/util.rs create mode 100644 crates/dtmm/src/ui/mod.rs rename crates/dtmm/src/{ => ui}/theme.rs (100%) rename crates/dtmm/src/{ => ui}/widget/container.rs (100%) rename crates/dtmm/src/{ => ui/widget}/controller.rs (100%) rename crates/dtmm/src/{ => ui}/widget/fill_container.rs (100%) rename crates/dtmm/src/{ => ui}/widget/mod.rs (94%) rename crates/dtmm/src/{ => ui}/widget/table_select.rs (100%) rename crates/dtmm/src/{main_window.rs => ui/window/main.rs} (93%) rename crates/dtmm/src/{util.rs => util/config.rs} (100%) rename crates/dtmm/src/{ => util}/log.rs (100%) diff --git a/crates/dtmm/src/engine.rs b/crates/dtmm/src/controller/engine.rs similarity index 86% rename from crates/dtmm/src/engine.rs rename to crates/dtmm/src/controller/engine.rs index 891778a..9956df8 100644 --- a/crates/dtmm/src/engine.rs +++ b/crates/dtmm/src/controller/engine.rs @@ -93,7 +93,7 @@ where #[tracing::instrument(skip_all)] async fn patch_game_settings(state: Arc) -> Result<()> { let settings_path = state - .get_game_dir() + .game_dir .join("bundle/application_settings/settings_common.ini"); let settings = read_file_with_backup(&settings_path) @@ -121,11 +121,11 @@ async fn patch_game_settings(state: Arc) -> Result<()> { Ok(()) } -#[tracing::instrument(skip_all, fields(package = info.get_name()))] +#[tracing::instrument(skip_all, fields(package = info.name))] fn make_package(info: &PackageInfo) -> Result { - let mut pkg = Package::new(info.get_name().clone(), PathBuf::new()); + let mut pkg = Package::new(info.name.clone(), PathBuf::new()); - for f in info.get_files().iter() { + for f in &info.files { let mut it = f.rsplit('.'); let file_type = it .next() @@ -144,32 +144,28 @@ fn build_mod_data_lua(state: Arc) -> String { // DMF is handled explicitely by the loading procedures, as it actually drives most of that // and should therefore not show up in the load order. - for mod_info in state - .get_mods() - .iter() - .filter(|m| m.get_id() != "dml" && m.get_enabled()) - { + for mod_info in state.mods.iter().filter(|m| m.id != "dml" && m.enabled) { lua.push_str(" {\n name = \""); - lua.push_str(mod_info.get_name()); + lua.push_str(&mod_info.name); lua.push_str("\",\n id = \""); - lua.push_str(mod_info.get_id()); + lua.push_str(&mod_info.id); lua.push_str("\",\n run = function()\n"); - let resources = mod_info.get_resources(); - if resources.get_data().is_some() || resources.get_localization().is_some() { + let resources = &mod_info.resources; + if resources.data.is_some() || resources.localization.is_some() { lua.push_str(" new_mod(\""); - lua.push_str(mod_info.get_id()); + lua.push_str(&mod_info.id); lua.push_str("\", {\n init = \""); - lua.push_str(&resources.get_init().to_string_lossy()); + lua.push_str(&resources.init.to_string_lossy()); - if let Some(data) = resources.get_data() { + if let Some(data) = resources.data.as_ref() { lua.push_str("\",\n data = \""); lua.push_str(&data.to_string_lossy()); } - if let Some(localization) = resources.get_localization() { + if let Some(localization) = &resources.localization { lua.push_str("\",\n localization = \""); lua.push_str(&localization.to_string_lossy()); } @@ -177,15 +173,15 @@ fn build_mod_data_lua(state: Arc) -> String { lua.push_str("\",\n })\n"); } else { lua.push_str(" return dofile(\""); - lua.push_str(&resources.get_init().to_string_lossy()); + lua.push_str(&resources.init.to_string_lossy()); lua.push_str("\")\n"); } lua.push_str(" end,\n packages = {\n"); - for pkg_info in mod_info.get_packages() { + for pkg_info in &mod_info.packages { lua.push_str(" \""); - lua.push_str(pkg_info.get_name()); + lua.push_str(&pkg_info.name); lua.push_str("\",\n"); } @@ -201,10 +197,10 @@ fn build_mod_data_lua(state: Arc) -> String { #[tracing::instrument(skip_all)] async fn build_bundles(state: Arc) -> Result> { - let mut mod_bundle = Bundle::new(MOD_BUNDLE_NAME); + let mut mod_bundle = Bundle::new(MOD_BUNDLE_NAME.to_string()); let mut tasks = Vec::new(); - let bundle_dir = Arc::new(state.get_game_dir().join("bundle")); + let bundle_dir = Arc::new(state.game_dir.join("bundle")); let mut bundles = Vec::new(); @@ -220,17 +216,13 @@ async fn build_bundles(state: Arc) -> Result> { mod_bundle.add_file(file); } - for mod_info in state - .get_mods() - .iter() - .filter(|m| m.get_id() != "dml" && m.get_enabled()) - { - let span = tracing::trace_span!("building mod packages", name = mod_info.get_name()); + for mod_info in state.mods.iter().filter(|m| m.id != "dml" && m.enabled) { + let span = tracing::trace_span!("building mod packages", name = mod_info.name); let _enter = span.enter(); - let mod_dir = state.get_mod_dir().join(mod_info.get_id()); - for pkg_info in mod_info.get_packages() { - let span = tracing::trace_span!("building package", name = pkg_info.get_name()); + let mod_dir = state.get_mod_dir().join(&mod_info.id); + for pkg_info in &mod_info.packages { + let span = tracing::trace_span!("building package", name = pkg_info.name); let _enter = span.enter(); let pkg = make_package(pkg_info).wrap_err("failed to make package")?; @@ -239,24 +231,24 @@ async fn build_bundles(state: Arc) -> Result> { .to_binary() .wrap_err("failed to serialize package to binary")?; variant.set_data(bin); - let mut file = BundleFile::new(pkg_info.get_name().clone(), BundleFileType::Package); + let mut file = BundleFile::new(pkg_info.name.clone(), BundleFileType::Package); file.add_variant(variant); mod_bundle.add_file(file); - let bundle_name = Murmur64::hash(pkg_info.get_name()) + let bundle_name = Murmur64::hash(&pkg_info.name) .to_string() .to_ascii_lowercase(); let src = mod_dir.join(&bundle_name); let dest = bundle_dir.join(&bundle_name); - let pkg_name = pkg_info.get_name().clone(); - let mod_name = mod_info.get_name().clone(); + let pkg_name = pkg_info.name.clone(); + let mod_name = mod_info.name.clone(); // Explicitely drop the guard, so that we can move the span // into the async operation drop(_enter); - let ctx = state.get_ctx().clone(); + let ctx = state.ctx.clone(); let task = async move { let bundle = { @@ -322,7 +314,7 @@ async fn build_bundles(state: Arc) -> Result> { #[tracing::instrument(skip_all)] async fn patch_boot_bundle(state: Arc) -> Result> { - let bundle_dir = Arc::new(state.get_game_dir().join("bundle")); + let bundle_dir = Arc::new(state.game_dir.join("bundle")); let bundle_path = bundle_dir.join(format!("{:x}", Murmur64::hash(BOOT_BUNDLE_NAME.as_bytes()))); let mut bundles = Vec::with_capacity(2); @@ -332,7 +324,7 @@ async fn patch_boot_bundle(state: Arc) -> Result> { .await .wrap_err("failed to read boot bundle")?; - Bundle::from_binary(&state.get_ctx(), BOOT_BUNDLE_NAME.to_string(), bin) + Bundle::from_binary(&state.ctx, BOOT_BUNDLE_NAME.to_string(), bin) .wrap_err("failed to parse boot bundle") } .instrument(tracing::trace_span!("read boot bundle")) @@ -346,9 +338,9 @@ async fn patch_boot_bundle(state: Arc) -> Result> { let mut pkg = Package::new(MOD_BUNDLE_NAME.to_string(), PathBuf::new()); - for mod_info in state.get_mods() { - for pkg_info in mod_info.get_packages() { - pkg.add_file(BundleFileType::Package, pkg_info.get_name()); + for mod_info in &state.mods { + for pkg_info in &mod_info.packages { + pkg.add_file(BundleFileType::Package, &pkg_info.name); } } @@ -369,32 +361,28 @@ async fn patch_boot_bundle(state: Arc) -> Result> { let mut variant = BundleFileVariant::new(); - let mods = state.get_mods(); - let mod_info = mods + let mod_info = state + .mods .iter() - .find(|m| m.get_id() == "dml") + .find(|m| m.id == "dml") .ok_or_else(|| eyre::eyre!("DML not found in mod list"))?; let pkg_info = mod_info - .get_packages() + .packages .get(0) .ok_or_else(|| eyre::eyre!("invalid mod package for DML")) .with_suggestion(|| "Re-download and import the newest version.".to_string())?; - let bundle_name = Murmur64::hash(pkg_info.get_name()) + let bundle_name = Murmur64::hash(&pkg_info.name) .to_string() .to_ascii_lowercase(); - let src = state - .get_mod_dir() - .join(mod_info.get_id()) - .join(&bundle_name); + let src = state.get_mod_dir().join(&mod_info.id).join(&bundle_name); { - let ctx = state.get_ctx(); let bin = fs::read(&src) .await .wrap_err_with(|| format!("failed to read bundle file '{}'", src.display()))?; - let name = Bundle::get_name_from_path(&ctx, &src); + let name = Bundle::get_name_from_path(&state.ctx, &src); - let dml_bundle = Bundle::from_binary(&ctx, name, bin) + let dml_bundle = Bundle::from_binary(&state.ctx, name, bin) .wrap_err_with(|| format!("failed to parse bundle '{}'", src.display()))?; bundles.push(dml_bundle); @@ -402,8 +390,8 @@ async fn patch_boot_bundle(state: Arc) -> Result> { { let dest = bundle_dir.join(&bundle_name); - let pkg_name = pkg_info.get_name().clone(); - let mod_name = mod_info.get_name().clone(); + let pkg_name = pkg_info.name.clone(); + let mod_name = mod_info.name.clone(); tracing::debug!( "Copying bundle {} for mod {}: {} -> {}", @@ -441,7 +429,7 @@ async fn patch_boot_bundle(state: Arc) -> Result> { let span = tracing::debug_span!("Importing mod main script"); let _enter = span.enter(); - let lua = include_str!("../assets/mod_main.lua"); + let lua = include_str!("../../assets/mod_main.lua"); let lua = CString::new(lua).wrap_err("failed to build CString from mod main Lua string")?; let file = lua::compile(MOD_BOOT_SCRIPT, &lua).wrap_err("failed to compile mod main Lua file")?; @@ -467,7 +455,7 @@ async fn patch_boot_bundle(state: Arc) -> Result> { #[tracing::instrument(skip_all, fields(bundles = bundles.len()))] async fn patch_bundle_database(state: Arc, bundles: Vec) -> Result<()> { - let bundle_dir = Arc::new(state.get_game_dir().join("bundle")); + let bundle_dir = Arc::new(state.game_dir.join("bundle")); let database_path = bundle_dir.join(BUNDLE_DATABASE_NAME); let mut db = { @@ -501,16 +489,15 @@ async fn patch_bundle_database(state: Arc, bundles: Vec) -> Resul } #[tracing::instrument(skip_all, fields( - game_dir = %state.get_game_dir().display(), - mods = state.get_mods().len() + game_dir = %state.game_dir.display(), + mods = state.mods.len() ))] pub(crate) async fn deploy_mods(state: State) -> Result<()> { let state = Arc::new(state); { - let mods = state.get_mods(); - let first = mods.get(0); - if first.is_none() || !(first.unwrap().get_id() == "dml" && first.unwrap().get_enabled()) { + let first = state.mods.get(0); + if first.is_none() || !(first.unwrap().id == "dml" && first.unwrap().enabled) { // TODO: Add a suggestion where to get it, once that's published eyre::bail!("'Darktide Mod Loader' needs to be installed, enabled and at the top of the load order"); } @@ -518,8 +505,8 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> { tracing::info!( "Deploying {} mods to {}", - state.get_mods().len(), - state.get_game_dir().join("bundle").display() + state.mods.len(), + state.game_dir.join("bundle").display() ); tracing::info!("Build mod bundles"); @@ -550,7 +537,7 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> { #[tracing::instrument(skip(state))] pub(crate) async fn reset_mod_deployment(state: State) -> Result<()> { let paths = [BUNDLE_DATABASE_NAME, BOOT_BUNDLE_NAME]; - let bundle_dir = state.get_game_dir().join("bundle"); + let bundle_dir = state.game_dir.join("bundle"); tracing::info!("Resetting mod deployment in {}", bundle_dir.display()); @@ -664,7 +651,7 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result #[tracing::instrument(skip(state))] pub(crate) async fn delete_mod(state: State, info: &ModInfo) -> Result<()> { - let mod_dir = state.get_mod_dir().join(info.get_id()); + let mod_dir = state.get_mod_dir().join(&info.id); fs::remove_dir_all(&mod_dir) .await .wrap_err_with(|| format!("failed to remove directory {}", mod_dir.display()))?; diff --git a/crates/dtmm/src/worker.rs b/crates/dtmm/src/controller/worker.rs similarity index 99% rename from crates/dtmm/src/worker.rs rename to crates/dtmm/src/controller/worker.rs index 4715480..58b3827 100644 --- a/crates/dtmm/src/worker.rs +++ b/crates/dtmm/src/controller/worker.rs @@ -6,7 +6,7 @@ use tokio::runtime::Runtime; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::RwLock; -use crate::engine::*; +use crate::controller::engine::*; use crate::state::*; async fn handle_action( diff --git a/crates/dtmm/src/main.rs b/crates/dtmm/src/main.rs index 6a3252a..bf38c65 100644 --- a/crates/dtmm/src/main.rs +++ b/crates/dtmm/src/main.rs @@ -12,24 +12,25 @@ use color_eyre::{Report, Result}; use druid::AppLauncher; use tokio::sync::RwLock; +use crate::controller::worker::work_thread; use crate::state::{Delegate, State}; -use crate::worker::work_thread; -mod controller; -mod engine; -mod log; -mod main_window; +mod controller { + pub mod engine; + pub mod worker; +} mod state; -mod theme; -mod util; -mod widget; -mod worker; +mod util { + pub mod config; + pub mod log; +} +mod ui; #[tracing::instrument] fn main() -> Result<()> { color_eyre::install()?; - let default_config_path = util::get_default_config_path(); + let default_config_path = util::config::get_default_config_path(); tracing::trace!(default_config_path = %default_config_path.display()); @@ -51,21 +52,21 @@ fn main() -> Result<()> { .get_matches(); let (log_tx, log_rx) = tokio::sync::mpsc::unbounded_channel(); - log::create_tracing_subscriber(log_tx); + util::log::create_tracing_subscriber(log_tx); unsafe { oodle_sys::init(matches.get_one::("oodle")); } - let config = - util::read_config(&default_config_path, &matches).wrap_err("failed to read config file")?; + let config = util::config::read_config(&default_config_path, &matches) + .wrap_err("failed to read config file")?; let initial_state = State::new(config); let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel(); let delegate = Delegate::new(action_tx); - let launcher = AppLauncher::with_window(main_window::new()).delegate(delegate); + let launcher = AppLauncher::with_window(ui::window::main::new()).delegate(delegate); let event_sink = launcher.get_external_handle(); std::thread::spawn(move || { diff --git a/crates/dtmm/src/state.rs b/crates/dtmm/src/state.rs deleted file mode 100644 index 71f66c2..0000000 --- a/crates/dtmm/src/state.rs +++ /dev/null @@ -1,510 +0,0 @@ -use std::path::PathBuf; -use std::sync::Arc; - -use druid::im::Vector; -use druid::text::Formatter; -use druid::{ - AppDelegate, Command, Data, DelegateCtx, Env, FileInfo, Handled, Lens, Selector, SingleUse, - Target, -}; -use dtmt_shared::ModConfig; -use tokio::sync::mpsc::UnboundedSender; - -use crate::util::Config; - -pub(crate) const ACTION_SELECT_MOD: Selector = Selector::new("dtmm.action.select-mod"); -pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up"); -pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector = - Selector::new("dtmm.action.selected-mod-down"); -pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector> = - Selector::new("dtmm.action.srart-delete-selected-mod"); -pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector> = - Selector::new("dtmm.action.finish-delete-selected-mod"); - -pub(crate) const ACTION_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy"); -pub(crate) const ACTION_FINISH_DEPLOY: Selector = Selector::new("dtmm.action.finish-deploy"); - -pub(crate) const ACTION_START_RESET_DEPLOYMENT: Selector = - Selector::new("dtmm.action.start-reset-deployment"); -pub(crate) const ACTION_FINISH_RESET_DEPLOYMENT: Selector = - Selector::new("dtmm.action.finish-reset-deployment"); - -pub(crate) const ACTION_ADD_MOD: Selector = Selector::new("dtmm.action.add-mod"); -pub(crate) const ACTION_FINISH_ADD_MOD: Selector> = - Selector::new("dtmm.action.finish-add-mod"); - -pub(crate) const ACTION_LOG: Selector> = Selector::new("dtmm.action.log"); - -#[derive(Copy, Clone, Data, Debug, PartialEq)] -pub(crate) enum View { - Mods, - Settings, - About, -} - -impl Default for View { - fn default() -> Self { - Self::Mods - } -} - -#[derive(Clone, Data, Debug)] -pub struct PackageInfo { - name: String, - files: Vector, -} - -impl PackageInfo { - pub fn new(name: String, files: Vector) -> Self { - Self { name, files } - } - - pub fn get_name(&self) -> &String { - &self.name - } - - pub fn get_files(&self) -> &Vector { - &self.files - } -} - -#[derive(Clone, Debug)] -pub(crate) struct ModResourceInfo { - init: PathBuf, - data: Option, - localization: Option, -} - -impl ModResourceInfo { - pub(crate) fn get_init(&self) -> &PathBuf { - &self.init - } - - pub(crate) fn get_data(&self) -> Option<&PathBuf> { - self.data.as_ref() - } - - pub(crate) fn get_localization(&self) -> Option<&PathBuf> { - self.localization.as_ref() - } -} - -#[derive(Clone, Data, Debug, Lens)] -pub(crate) struct ModInfo { - id: String, - name: String, - description: Arc, - enabled: bool, - #[lens(ignore)] - #[data(ignore)] - packages: Vector, - #[lens(ignore)] - #[data(ignore)] - resources: ModResourceInfo, -} - -impl ModInfo { - pub fn new(cfg: ModConfig, packages: Vector) -> Self { - Self { - id: cfg.id, - name: cfg.name, - description: Arc::new(cfg.description), - enabled: false, - packages, - resources: ModResourceInfo { - init: cfg.resources.init, - data: cfg.resources.data, - localization: cfg.resources.localization, - }, - } - } - - pub fn get_packages(&self) -> &Vector { - &self.packages - } - - pub(crate) fn get_name(&self) -> &String { - &self.name - } - - pub(crate) fn get_id(&self) -> &String { - &self.id - } - - pub(crate) fn get_enabled(&self) -> bool { - self.enabled - } - - pub(crate) fn get_resources(&self) -> &ModResourceInfo { - &self.resources - } -} - -impl PartialEq for ModInfo { - fn eq(&self, other: &Self) -> bool { - self.name.eq(&other.name) - } -} - -#[derive(Clone, Data, Lens)] -pub(crate) struct State { - current_view: View, - mods: Vector, - selected_mod_index: Option, - is_deployment_in_progress: bool, - is_reset_in_progress: bool, - game_dir: Arc, - data_dir: Arc, - ctx: Arc, - log: Arc, -} - -impl State { - #[allow(non_upper_case_globals)] - pub const selected_mod: SelectedModLens = SelectedModLens; - - pub fn new(config: Config) -> Self { - let ctx = sdk::Context::new(); - - Self { - ctx: Arc::new(ctx), - current_view: View::default(), - mods: Vector::new(), - selected_mod_index: None, - is_deployment_in_progress: false, - is_reset_in_progress: false, - game_dir: Arc::new(config.game_dir().cloned().unwrap_or_default()), - data_dir: Arc::new(config.data_dir().cloned().unwrap_or_default()), - log: Arc::new(String::new()), - } - } - - pub fn get_current_view(&self) -> View { - self.current_view - } - - pub fn set_current_view(&mut self, view: View) { - self.current_view = view; - } - - pub fn get_mods(&self) -> Vector { - self.mods.clone() - } - - pub fn select_mod(&mut self, index: usize) { - self.selected_mod_index = Some(index); - } - - pub fn add_mod(&mut self, info: ModInfo) { - if let Some(pos) = self.mods.index_of(&info) { - self.mods.set(pos, info); - self.selected_mod_index = Some(pos); - } else { - self.mods.push_back(info); - self.selected_mod_index = Some(self.mods.len() - 1); - } - } - - pub fn can_move_mod_down(&self) -> bool { - self.selected_mod_index - .map(|i| i < (self.mods.len().saturating_sub(1))) - .unwrap_or(false) - } - - pub fn can_move_mod_up(&self) -> bool { - self.selected_mod_index.map(|i| i > 0).unwrap_or(false) - } - - pub fn can_deploy_mods(&self) -> bool { - !self.is_deployment_in_progress - } - - pub fn can_reset_deployment(&self) -> bool { - !self.is_reset_in_progress - } - - pub(crate) fn get_game_dir(&self) -> &PathBuf { - &self.game_dir - } - - pub(crate) fn get_mod_dir(&self) -> PathBuf { - self.data_dir.join("mods") - } - - pub(crate) fn get_ctx(&self) -> Arc { - self.ctx.clone() - } - - pub(crate) fn add_log_line(&mut self, line: String) { - let log = Arc::make_mut(&mut self.log); - log.push_str(&line); - } -} - -pub(crate) struct SelectedModLens; - -impl Lens> for SelectedModLens { - #[tracing::instrument(name = "SelectedModLens::with", skip_all)] - fn with) -> V>(&self, data: &State, f: F) -> V { - let info = data - .selected_mod_index - .and_then(|i| data.mods.get(i).cloned()); - - f(&info) - } - - #[tracing::instrument(name = "SelectedModLens::with_mut", skip_all)] - fn with_mut) -> V>(&self, data: &mut State, f: F) -> V { - match data.selected_mod_index { - Some(i) => { - let mut info = data.mods.get_mut(i).cloned(); - let ret = f(&mut info); - - if let Some(info) = info { - // TODO: Figure out a way to check for equality and - // only update when needed - data.mods.set(i, info); - } else { - data.selected_mod_index = None; - } - - ret - } - None => f(&mut None), - } - } -} - -/// A Lens that maps an `im::Vector` to `im::Vector<(usize, T)>`, -/// where each element in the destination vector includes its index in the -/// source vector. -pub(crate) struct IndexedVectorLens; - -impl Lens, Vector<(usize, T)>> for IndexedVectorLens { - #[tracing::instrument(name = "IndexedVectorLens::with", skip_all)] - fn with) -> V>(&self, values: &Vector, f: F) -> V { - let indexed = values - .iter() - .enumerate() - .map(|(i, val)| (i, val.clone())) - .collect(); - f(&indexed) - } - - #[tracing::instrument(name = "IndexedVectorLens::with_mut", skip_all)] - fn with_mut) -> V>( - &self, - values: &mut Vector, - f: F, - ) -> V { - let mut indexed = values - .iter() - .enumerate() - .map(|(i, val)| (i, val.clone())) - .collect(); - let ret = f(&mut indexed); - - *values = indexed.into_iter().map(|(_i, val)| val).collect(); - - ret - } -} - -pub(crate) enum AsyncAction { - DeployMods(State), - ResetDeployment(State), - AddMod((State, FileInfo)), - DeleteMod((State, ModInfo)), -} - -pub(crate) struct Delegate { - sender: UnboundedSender, -} - -impl Delegate { - pub fn new(sender: UnboundedSender) -> Self { - Self { sender } - } -} - -impl AppDelegate for Delegate { - #[tracing::instrument(name = "Delegate", skip_all)] - fn command( - &mut self, - _ctx: &mut DelegateCtx, - _target: Target, - cmd: &Command, - state: &mut State, - _env: &Env, - ) -> Handled { - match cmd { - cmd if cmd.is(ACTION_START_DEPLOY) => { - if self - .sender - .send(AsyncAction::DeployMods(state.clone())) - .is_ok() - { - state.is_deployment_in_progress = true; - } else { - tracing::error!("Failed to queue action to deploy mods"); - } - - Handled::Yes - } - cmd if cmd.is(ACTION_FINISH_DEPLOY) => { - state.is_deployment_in_progress = false; - Handled::Yes - } - cmd if cmd.is(ACTION_START_RESET_DEPLOYMENT) => { - if self - .sender - .send(AsyncAction::ResetDeployment(state.clone())) - .is_ok() - { - state.is_reset_in_progress = true; - } else { - tracing::error!("Failed to queue action to reset mod deployment"); - } - - Handled::Yes - } - cmd if cmd.is(ACTION_FINISH_RESET_DEPLOYMENT) => { - state.is_reset_in_progress = false; - Handled::Yes - } - cmd if cmd.is(ACTION_SELECT_MOD) => { - let index = cmd - .get(ACTION_SELECT_MOD) - .expect("command type matched but didn't contain the expected value"); - - state.select_mod(*index); - Handled::Yes - } - cmd if cmd.is(ACTION_SELECTED_MOD_UP) => { - let Some(i) = state.selected_mod_index else { - return Handled::No; - }; - - let len = state.mods.len(); - if len == 0 || i == 0 { - return Handled::No; - } - - state.mods.swap(i, i - 1); - state.selected_mod_index = Some(i - 1); - Handled::Yes - } - cmd if cmd.is(ACTION_SELECTED_MOD_DOWN) => { - let Some(i) = state.selected_mod_index else { - return Handled::No; - }; - - let len = state.mods.len(); - if len == 0 || i == usize::MAX || i >= len - 1 { - return Handled::No; - } - - state.mods.swap(i, i + 1); - state.selected_mod_index = Some(i + 1); - Handled::Yes - } - cmd if cmd.is(ACTION_START_DELETE_SELECTED_MOD) => { - let info = cmd - .get(ACTION_START_DELETE_SELECTED_MOD) - .and_then(|info| info.take()) - .expect("command type matched but didn't contain the expected value"); - if self - .sender - .send(AsyncAction::DeleteMod((state.clone(), info))) - .is_ok() - { - state.is_deployment_in_progress = true; - } else { - tracing::error!("Failed to queue action to deploy mods"); - } - - Handled::Yes - } - cmd if cmd.is(ACTION_FINISH_DELETE_SELECTED_MOD) => { - let info = cmd - .get(ACTION_FINISH_DELETE_SELECTED_MOD) - .and_then(|info| info.take()) - .expect("command type matched but didn't contain the expected value"); - let mods = state.get_mods(); - let found = mods - .iter() - .enumerate() - .find(|(_, i)| i.get_id() == info.get_id()); - let Some((index, _)) = found else { - return Handled::No; - }; - - state.mods.remove(index); - Handled::Yes - } - cmd if cmd.is(ACTION_ADD_MOD) => { - let info = cmd - .get(ACTION_ADD_MOD) - .expect("command type matched but didn't contain the expected value"); - if let Err(err) = self - .sender - .send(AsyncAction::AddMod((state.clone(), info.clone()))) - { - tracing::error!("Failed to add mod: {}", err); - } - Handled::Yes - } - cmd if cmd.is(ACTION_FINISH_ADD_MOD) => { - let info = cmd - .get(ACTION_FINISH_ADD_MOD) - .expect("command type matched but didn't contain the expected value"); - if let Some(info) = info.take() { - state.add_mod(info); - } - Handled::Yes - } - cmd if cmd.is(ACTION_LOG) => { - let line = cmd - .get(ACTION_LOG) - .expect("command type matched but didn't contain the expected value"); - if let Some(line) = line.take() { - state.add_log_line(line); - } - Handled::Yes - } - cmd => { - if cfg!(debug_assertions) { - tracing::warn!("Unknown command: {:?}", cmd); - } - Handled::No - } - } - } -} - -pub(crate) struct PathBufFormatter; - -impl PathBufFormatter { - pub fn new() -> Self { - Self {} - } -} - -impl Formatter> for PathBufFormatter { - fn format(&self, value: &Arc) -> String { - value.display().to_string() - } - - fn validate_partial_input( - &self, - _input: &str, - _sel: &druid::text::Selection, - ) -> druid::text::Validation { - druid::text::Validation::success() - } - - fn value(&self, input: &str) -> Result, druid::text::ValidationError> { - let p = PathBuf::from(input); - Ok(Arc::new(p)) - } -} diff --git a/crates/dtmm/src/state/data.rs b/crates/dtmm/src/state/data.rs new file mode 100644 index 0000000..b016b5a --- /dev/null +++ b/crates/dtmm/src/state/data.rs @@ -0,0 +1,144 @@ +use std::{path::PathBuf, sync::Arc}; + +use druid::{im::Vector, Data, Lens}; +use dtmt_shared::ModConfig; + +use crate::util::config::Config; + +use super::SelectedModLens; + +#[derive(Copy, Clone, Data, Debug, PartialEq)] +pub(crate) enum View { + Mods, + Settings, + About, +} + +impl Default for View { + fn default() -> Self { + Self::Mods + } +} + +#[derive(Clone, Data, Debug)] +pub struct PackageInfo { + pub name: String, + pub files: Vector, +} + +impl PackageInfo { + pub fn new(name: String, files: Vector) -> Self { + Self { name, files } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct ModResourceInfo { + pub init: PathBuf, + pub data: Option, + pub localization: Option, +} + +#[derive(Clone, Data, Debug, Lens)] +pub(crate) struct ModInfo { + pub id: String, + pub name: String, + pub description: Arc, + pub enabled: bool, + #[lens(ignore)] + #[data(ignore)] + pub packages: Vector, + #[lens(ignore)] + #[data(ignore)] + pub resources: ModResourceInfo, +} + +impl ModInfo { + pub fn new(cfg: ModConfig, packages: Vector) -> Self { + Self { + id: cfg.id, + name: cfg.name, + description: Arc::new(cfg.description), + enabled: false, + packages, + resources: ModResourceInfo { + init: cfg.resources.init, + data: cfg.resources.data, + localization: cfg.resources.localization, + }, + } + } +} + +impl PartialEq for ModInfo { + fn eq(&self, other: &Self) -> bool { + self.name.eq(&other.name) + } +} + +#[derive(Clone, Data, Lens)] +pub(crate) struct State { + pub current_view: View, + pub mods: Vector, + pub selected_mod_index: Option, + pub is_deployment_in_progress: bool, + pub is_reset_in_progress: bool, + pub game_dir: Arc, + pub data_dir: Arc, + pub ctx: Arc, + pub log: Arc, +} + +impl State { + #[allow(non_upper_case_globals)] + pub const selected_mod: SelectedModLens = SelectedModLens; + + pub fn new(config: Config) -> Self { + let ctx = sdk::Context::new(); + + Self { + ctx: Arc::new(ctx), + current_view: View::default(), + mods: Vector::new(), + selected_mod_index: None, + is_deployment_in_progress: false, + is_reset_in_progress: false, + game_dir: Arc::new(config.game_dir().cloned().unwrap_or_default()), + data_dir: Arc::new(config.data_dir().cloned().unwrap_or_default()), + log: Arc::new(String::new()), + } + } + + pub fn select_mod(&mut self, index: usize) { + self.selected_mod_index = Some(index); + } + + pub fn add_mod(&mut self, info: ModInfo) { + if let Some(pos) = self.mods.index_of(&info) { + self.mods.set(pos, info); + self.selected_mod_index = Some(pos); + } else { + self.mods.push_back(info); + self.selected_mod_index = Some(self.mods.len() - 1); + } + } + + pub fn can_move_mod_down(&self) -> bool { + self.selected_mod_index + .map(|i| i < (self.mods.len().saturating_sub(1))) + .unwrap_or(false) + } + + pub fn can_move_mod_up(&self) -> bool { + self.selected_mod_index.map(|i| i > 0).unwrap_or(false) + } + + pub(crate) fn get_mod_dir(&self) -> PathBuf { + self.data_dir.join("mods") + } + + pub(crate) fn add_log_line(&mut self, line: String) { + let log = Arc::make_mut(&mut self.log); + log.push_str(&line); + } +} diff --git a/crates/dtmm/src/state/delegate.rs b/crates/dtmm/src/state/delegate.rs new file mode 100644 index 0000000..9a2a1ef --- /dev/null +++ b/crates/dtmm/src/state/delegate.rs @@ -0,0 +1,197 @@ +use druid::{ + AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse, Target, +}; +use tokio::sync::mpsc::UnboundedSender; + +use super::{ModInfo, State}; + +pub(crate) const ACTION_SELECT_MOD: Selector = Selector::new("dtmm.action.select-mod"); +pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up"); +pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector = + Selector::new("dtmm.action.selected-mod-down"); +pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector> = + Selector::new("dtmm.action.srart-delete-selected-mod"); +pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector> = + Selector::new("dtmm.action.finish-delete-selected-mod"); + +pub(crate) const ACTION_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy"); +pub(crate) const ACTION_FINISH_DEPLOY: Selector = Selector::new("dtmm.action.finish-deploy"); + +pub(crate) const ACTION_START_RESET_DEPLOYMENT: Selector = + Selector::new("dtmm.action.start-reset-deployment"); +pub(crate) const ACTION_FINISH_RESET_DEPLOYMENT: Selector = + Selector::new("dtmm.action.finish-reset-deployment"); + +pub(crate) const ACTION_ADD_MOD: Selector = Selector::new("dtmm.action.add-mod"); +pub(crate) const ACTION_FINISH_ADD_MOD: Selector> = + Selector::new("dtmm.action.finish-add-mod"); + +pub(crate) const ACTION_LOG: Selector> = Selector::new("dtmm.action.log"); + +pub(crate) enum AsyncAction { + DeployMods(State), + ResetDeployment(State), + AddMod((State, FileInfo)), + DeleteMod((State, ModInfo)), +} + +pub(crate) struct Delegate { + sender: UnboundedSender, +} + +impl Delegate { + pub fn new(sender: UnboundedSender) -> Self { + Self { sender } + } +} + +impl AppDelegate for Delegate { + #[tracing::instrument(name = "Delegate", skip_all)] + fn command( + &mut self, + _ctx: &mut DelegateCtx, + _target: Target, + cmd: &Command, + state: &mut State, + _env: &Env, + ) -> Handled { + match cmd { + cmd if cmd.is(ACTION_START_DEPLOY) => { + if self + .sender + .send(AsyncAction::DeployMods(state.clone())) + .is_ok() + { + state.is_deployment_in_progress = true; + } else { + tracing::error!("Failed to queue action to deploy mods"); + } + + Handled::Yes + } + cmd if cmd.is(ACTION_FINISH_DEPLOY) => { + state.is_deployment_in_progress = false; + Handled::Yes + } + cmd if cmd.is(ACTION_START_RESET_DEPLOYMENT) => { + if self + .sender + .send(AsyncAction::ResetDeployment(state.clone())) + .is_ok() + { + state.is_reset_in_progress = true; + } else { + tracing::error!("Failed to queue action to reset mod deployment"); + } + + Handled::Yes + } + cmd if cmd.is(ACTION_FINISH_RESET_DEPLOYMENT) => { + state.is_reset_in_progress = false; + Handled::Yes + } + cmd if cmd.is(ACTION_SELECT_MOD) => { + let index = cmd + .get(ACTION_SELECT_MOD) + .expect("command type matched but didn't contain the expected value"); + + state.select_mod(*index); + Handled::Yes + } + cmd if cmd.is(ACTION_SELECTED_MOD_UP) => { + let Some(i) = state.selected_mod_index else { + return Handled::No; + }; + + let len = state.mods.len(); + if len == 0 || i == 0 { + return Handled::No; + } + + state.mods.swap(i, i - 1); + state.selected_mod_index = Some(i - 1); + Handled::Yes + } + cmd if cmd.is(ACTION_SELECTED_MOD_DOWN) => { + let Some(i) = state.selected_mod_index else { + return Handled::No; + }; + + let len = state.mods.len(); + if len == 0 || i == usize::MAX || i >= len - 1 { + return Handled::No; + } + + state.mods.swap(i, i + 1); + state.selected_mod_index = Some(i + 1); + Handled::Yes + } + cmd if cmd.is(ACTION_START_DELETE_SELECTED_MOD) => { + let info = cmd + .get(ACTION_START_DELETE_SELECTED_MOD) + .and_then(|info| info.take()) + .expect("command type matched but didn't contain the expected value"); + if self + .sender + .send(AsyncAction::DeleteMod((state.clone(), info))) + .is_ok() + { + state.is_deployment_in_progress = true; + } else { + tracing::error!("Failed to queue action to deploy mods"); + } + + Handled::Yes + } + cmd if cmd.is(ACTION_FINISH_DELETE_SELECTED_MOD) => { + let info = cmd + .get(ACTION_FINISH_DELETE_SELECTED_MOD) + .and_then(|info| info.take()) + .expect("command type matched but didn't contain the expected value"); + let found = state.mods.iter().enumerate().find(|(_, i)| i.id == info.id); + let Some((index, _)) = found else { + return Handled::No; + }; + + state.mods.remove(index); + Handled::Yes + } + cmd if cmd.is(ACTION_ADD_MOD) => { + let info = cmd + .get(ACTION_ADD_MOD) + .expect("command type matched but didn't contain the expected value"); + if let Err(err) = self + .sender + .send(AsyncAction::AddMod((state.clone(), info.clone()))) + { + tracing::error!("Failed to add mod: {}", err); + } + Handled::Yes + } + cmd if cmd.is(ACTION_FINISH_ADD_MOD) => { + let info = cmd + .get(ACTION_FINISH_ADD_MOD) + .expect("command type matched but didn't contain the expected value"); + if let Some(info) = info.take() { + state.add_mod(info); + } + Handled::Yes + } + cmd if cmd.is(ACTION_LOG) => { + let line = cmd + .get(ACTION_LOG) + .expect("command type matched but didn't contain the expected value"); + if let Some(line) = line.take() { + state.add_log_line(line); + } + Handled::Yes + } + cmd => { + if cfg!(debug_assertions) { + tracing::warn!("Unknown command: {:?}", cmd); + } + Handled::No + } + } + } +} diff --git a/crates/dtmm/src/state/lens.rs b/crates/dtmm/src/state/lens.rs new file mode 100644 index 0000000..6c457a4 --- /dev/null +++ b/crates/dtmm/src/state/lens.rs @@ -0,0 +1,73 @@ +use druid::im::Vector; +use druid::{Data, Lens}; + +use super::{ModInfo, State}; + +pub(crate) struct SelectedModLens; + +impl Lens> for SelectedModLens { + #[tracing::instrument(name = "SelectedModLens::with", skip_all)] + fn with) -> V>(&self, data: &State, f: F) -> V { + let info = data + .selected_mod_index + .and_then(|i| data.mods.get(i).cloned()); + + f(&info) + } + + #[tracing::instrument(name = "SelectedModLens::with_mut", skip_all)] + fn with_mut) -> V>(&self, data: &mut State, f: F) -> V { + match data.selected_mod_index { + Some(i) => { + let mut info = data.mods.get_mut(i).cloned(); + let ret = f(&mut info); + + if let Some(info) = info { + // TODO: Figure out a way to check for equality and + // only update when needed + data.mods.set(i, info); + } else { + data.selected_mod_index = None; + } + + ret + } + None => f(&mut None), + } + } +} + +/// A Lens that maps an `im::Vector` to `im::Vector<(usize, T)>`, +/// where each element in the destination vector includes its index in the +/// source vector. +pub(crate) struct IndexedVectorLens; + +impl Lens, Vector<(usize, T)>> for IndexedVectorLens { + #[tracing::instrument(name = "IndexedVectorLens::with", skip_all)] + fn with) -> V>(&self, values: &Vector, f: F) -> V { + let indexed = values + .iter() + .enumerate() + .map(|(i, val)| (i, val.clone())) + .collect(); + f(&indexed) + } + + #[tracing::instrument(name = "IndexedVectorLens::with_mut", skip_all)] + fn with_mut) -> V>( + &self, + values: &mut Vector, + f: F, + ) -> V { + let mut indexed = values + .iter() + .enumerate() + .map(|(i, val)| (i, val.clone())) + .collect(); + let ret = f(&mut indexed); + + *values = indexed.into_iter().map(|(_i, val)| val).collect(); + + ret + } +} diff --git a/crates/dtmm/src/state/mod.rs b/crates/dtmm/src/state/mod.rs new file mode 100644 index 0000000..1586e3c --- /dev/null +++ b/crates/dtmm/src/state/mod.rs @@ -0,0 +1,9 @@ +mod data; +mod delegate; +mod lens; +mod util; + +pub(crate) use data::*; +pub(crate) use delegate::*; +pub(crate) use lens::*; +pub(crate) use util::*; diff --git a/crates/dtmm/src/state/util.rs b/crates/dtmm/src/state/util.rs new file mode 100644 index 0000000..804b751 --- /dev/null +++ b/crates/dtmm/src/state/util.rs @@ -0,0 +1,31 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use druid::text::Formatter; + +pub(crate) struct PathBufFormatter; + +impl PathBufFormatter { + pub fn new() -> Self { + Self {} + } +} + +impl Formatter> for PathBufFormatter { + fn format(&self, value: &Arc) -> String { + value.display().to_string() + } + + fn validate_partial_input( + &self, + _input: &str, + _sel: &druid::text::Selection, + ) -> druid::text::Validation { + druid::text::Validation::success() + } + + fn value(&self, input: &str) -> Result, druid::text::ValidationError> { + let p = PathBuf::from(input); + Ok(Arc::new(p)) + } +} diff --git a/crates/dtmm/src/ui/mod.rs b/crates/dtmm/src/ui/mod.rs new file mode 100644 index 0000000..cf8554f --- /dev/null +++ b/crates/dtmm/src/ui/mod.rs @@ -0,0 +1,5 @@ +pub mod theme; +pub mod widget; +pub mod window { + pub mod main; +} diff --git a/crates/dtmm/src/theme.rs b/crates/dtmm/src/ui/theme.rs similarity index 100% rename from crates/dtmm/src/theme.rs rename to crates/dtmm/src/ui/theme.rs diff --git a/crates/dtmm/src/widget/container.rs b/crates/dtmm/src/ui/widget/container.rs similarity index 100% rename from crates/dtmm/src/widget/container.rs rename to crates/dtmm/src/ui/widget/container.rs diff --git a/crates/dtmm/src/controller.rs b/crates/dtmm/src/ui/widget/controller.rs similarity index 100% rename from crates/dtmm/src/controller.rs rename to crates/dtmm/src/ui/widget/controller.rs diff --git a/crates/dtmm/src/widget/fill_container.rs b/crates/dtmm/src/ui/widget/fill_container.rs similarity index 100% rename from crates/dtmm/src/widget/fill_container.rs rename to crates/dtmm/src/ui/widget/fill_container.rs diff --git a/crates/dtmm/src/widget/mod.rs b/crates/dtmm/src/ui/widget/mod.rs similarity index 94% rename from crates/dtmm/src/widget/mod.rs rename to crates/dtmm/src/ui/widget/mod.rs index 9262d0a..8561c31 100644 --- a/crates/dtmm/src/widget/mod.rs +++ b/crates/dtmm/src/ui/widget/mod.rs @@ -3,6 +3,7 @@ use druid::{Data, Widget}; use self::fill_container::FillContainer; pub mod container; +pub mod controller; pub mod fill_container; pub trait ExtraWidgetExt: Widget + Sized + 'static { diff --git a/crates/dtmm/src/widget/table_select.rs b/crates/dtmm/src/ui/widget/table_select.rs similarity index 100% rename from crates/dtmm/src/widget/table_select.rs rename to crates/dtmm/src/ui/widget/table_select.rs diff --git a/crates/dtmm/src/main_window.rs b/crates/dtmm/src/ui/window/main.rs similarity index 93% rename from crates/dtmm/src/main_window.rs rename to crates/dtmm/src/ui/window/main.rs index 6fcbc87..1853d2e 100644 --- a/crates/dtmm/src/main_window.rs +++ b/crates/dtmm/src/ui/window/main.rs @@ -13,8 +13,8 @@ use crate::state::{ ACTION_ADD_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP, ACTION_SELECT_MOD, ACTION_START_DELETE_SELECTED_MOD, ACTION_START_DEPLOY, }; -use crate::theme; -use crate::widget::ExtraWidgetExt; +use crate::ui::theme; +use crate::ui::widget::ExtraWidgetExt; const TITLE: &str = "Darktide Mod Manager"; const WINDOW_SIZE: (f64, f64) = (800.0, 600.0); @@ -33,21 +33,19 @@ fn build_top_bar() -> impl Widget { .with_child( Flex::row() .with_child( - Button::new("Mods").on_click(|_ctx, state: &mut State, _env| { - state.set_current_view(View::Mods) - }), + Button::new("Mods") + .on_click(|_ctx, state: &mut State, _env| state.current_view = View::Mods), ) .with_default_spacer() .with_child( Button::new("Settings").on_click(|_ctx, state: &mut State, _env| { - state.set_current_view(View::Settings) + state.current_view = View::Settings; }), ) .with_default_spacer() .with_child( - Button::new("About").on_click(|_ctx, state: &mut State, _env| { - state.set_current_view(View::About) - }), + Button::new("About") + .on_click(|_ctx, state: &mut State, _env| state.current_view = View::About), ), ) .with_child( @@ -57,7 +55,7 @@ fn build_top_bar() -> impl Widget { .on_click(|ctx, _state: &mut State, _env| { ctx.submit_command(ACTION_START_DEPLOY); }) - .disabled_if(|data, _| !data.can_deploy_mods()), + .disabled_if(|data, _| !data.is_deployment_in_progress), ) .with_default_spacer() .with_child( @@ -65,7 +63,7 @@ fn build_top_bar() -> impl Widget { .on_click(|ctx, _state: &mut State, _env| { ctx.submit_command(ACTION_START_RESET_DEPLOYMENT); }) - .disabled_if(|data, _| !data.can_reset_deployment()), + .disabled_if(|data, _| !data.is_reset_in_progress), ), ) .padding(theme::TOP_BAR_INSETS) @@ -253,7 +251,7 @@ fn build_view_about() -> impl Widget { fn build_main() -> impl Widget { ViewSwitcher::new( - |state: &State, _env| state.get_current_view(), + |state: &State, _env| state.current_view, |selector, _state, _env| match selector { View::Mods => Box::new(build_view_mods()), View::Settings => Box::new(build_view_settings()), diff --git a/crates/dtmm/src/util.rs b/crates/dtmm/src/util/config.rs similarity index 100% rename from crates/dtmm/src/util.rs rename to crates/dtmm/src/util/config.rs diff --git a/crates/dtmm/src/log.rs b/crates/dtmm/src/util/log.rs similarity index 100% rename from crates/dtmm/src/log.rs rename to crates/dtmm/src/util/log.rs