Merge pull request 'Indicate when a deployment is necessary' (#49) from issue/32 into master
Reviewed-on: #49
This commit is contained in:
commit
f021e507b8
9 changed files with 106 additions and 80 deletions
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
- dtmt: split `build` into `build` and `package`
|
- dtmt: split `build` into `build` and `package`
|
||||||
- dtmt: implement deploying built bundles
|
- dtmt: implement deploying built bundles
|
||||||
|
- dtmm: indicate when a deployment is necessary
|
||||||
|
|
||||||
=== Fixed
|
=== Fixed
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{Cursor, ErrorKind, Read};
|
use std::io::{Cursor, ErrorKind, Read};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use color_eyre::eyre::{self, Context};
|
use color_eyre::eyre::{self, Context};
|
||||||
use color_eyre::{Help, Result};
|
use color_eyre::{Help, Result};
|
||||||
|
@ -107,7 +108,7 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo>
|
||||||
|
|
||||||
let packages = files
|
let packages = files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
|
.map(|(name, files)| Arc::new(PackageInfo::new(name, files.into_iter().collect())))
|
||||||
.collect();
|
.collect();
|
||||||
let info = ModInfo::new(mod_cfg, packages);
|
let info = ModInfo::new(mod_cfg, packages);
|
||||||
|
|
||||||
|
@ -171,14 +172,14 @@ async fn read_mod_dir_entry(res: Result<DirEntry>) -> Result<ModInfo> {
|
||||||
|
|
||||||
let packages = files
|
let packages = files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
|
.map(|(name, files)| Arc::new(PackageInfo::new(name, files.into_iter().collect())))
|
||||||
.collect();
|
.collect();
|
||||||
let info = ModInfo::new(cfg, packages);
|
let info = ModInfo::new(cfg, packages);
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(mod_order))]
|
#[tracing::instrument(skip(mod_order))]
|
||||||
pub(crate) fn load_mods<'a, P, S>(mod_dir: P, mod_order: S) -> Result<Vector<ModInfo>>
|
pub(crate) fn load_mods<'a, P, S>(mod_dir: P, mod_order: S) -> Result<Vector<Arc<ModInfo>>>
|
||||||
where
|
where
|
||||||
S: Iterator<Item = &'a LoadOrderEntry>,
|
S: Iterator<Item = &'a LoadOrderEntry>,
|
||||||
P: AsRef<Path> + std::fmt::Debug,
|
P: AsRef<Path> + std::fmt::Debug,
|
||||||
|
@ -214,7 +215,7 @@ where
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
if let Some(mut info) = mods.remove(&entry.id) {
|
if let Some(mut info) = mods.remove(&entry.id) {
|
||||||
info.enabled = entry.enabled;
|
info.enabled = entry.enabled;
|
||||||
Some(info)
|
Some(Arc::new(info))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ async fn handle_action(
|
||||||
.await
|
.await
|
||||||
.submit_command(
|
.submit_command(
|
||||||
ACTION_FINISH_ADD_MOD,
|
ACTION_FINISH_ADD_MOD,
|
||||||
SingleUse::new(mod_info),
|
SingleUse::new(Arc::new(mod_info)),
|
||||||
Target::Auto,
|
Target::Auto,
|
||||||
)
|
)
|
||||||
.expect("failed to send command");
|
.expect("failed to send command");
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl Default for View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Data, Debug)]
|
#[derive(Clone, Data, Debug, PartialEq)]
|
||||||
pub struct PackageInfo {
|
pub struct PackageInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub files: Vector<String>,
|
pub files: Vector<String>,
|
||||||
|
@ -29,14 +29,14 @@ impl PackageInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub(crate) struct ModResourceInfo {
|
pub(crate) struct ModResourceInfo {
|
||||||
pub init: PathBuf,
|
pub init: PathBuf,
|
||||||
pub data: Option<PathBuf>,
|
pub data: Option<PathBuf>,
|
||||||
pub localization: Option<PathBuf>,
|
pub localization: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Data, Debug, Lens)]
|
#[derive(Clone, Data, Debug, Lens, PartialEq)]
|
||||||
pub(crate) struct ModInfo {
|
pub(crate) struct ModInfo {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -44,14 +44,14 @@ pub(crate) struct ModInfo {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
#[lens(ignore)]
|
#[lens(ignore)]
|
||||||
#[data(ignore)]
|
#[data(ignore)]
|
||||||
pub packages: Vector<PackageInfo>,
|
pub packages: Vector<Arc<PackageInfo>>,
|
||||||
#[lens(ignore)]
|
#[lens(ignore)]
|
||||||
#[data(ignore)]
|
#[data(ignore)]
|
||||||
pub resources: ModResourceInfo,
|
pub resources: ModResourceInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModInfo {
|
impl ModInfo {
|
||||||
pub fn new(cfg: ModConfig, packages: Vector<PackageInfo>) -> Self {
|
pub fn new(cfg: ModConfig, packages: Vector<Arc<PackageInfo>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: cfg.id,
|
id: cfg.id,
|
||||||
name: cfg.name,
|
name: cfg.name,
|
||||||
|
@ -67,17 +67,12 @@ impl ModInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ModInfo {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.name.eq(&other.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Data, Lens)]
|
#[derive(Clone, Data, Lens)]
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
pub current_view: View,
|
pub current_view: View,
|
||||||
pub mods: Vector<ModInfo>,
|
pub mods: Vector<Arc<ModInfo>>,
|
||||||
pub selected_mod_index: Option<usize>,
|
pub selected_mod_index: Option<usize>,
|
||||||
|
pub dirty: bool,
|
||||||
pub is_deployment_in_progress: bool,
|
pub is_deployment_in_progress: bool,
|
||||||
pub is_reset_in_progress: bool,
|
pub is_reset_in_progress: bool,
|
||||||
pub is_save_in_progress: bool,
|
pub is_save_in_progress: bool,
|
||||||
|
@ -106,6 +101,7 @@ impl State {
|
||||||
current_view: View::default(),
|
current_view: View::default(),
|
||||||
mods: Vector::new(),
|
mods: Vector::new(),
|
||||||
selected_mod_index: None,
|
selected_mod_index: None,
|
||||||
|
dirty: false,
|
||||||
is_deployment_in_progress: false,
|
is_deployment_in_progress: false,
|
||||||
is_reset_in_progress: false,
|
is_reset_in_progress: false,
|
||||||
is_save_in_progress: false,
|
is_save_in_progress: false,
|
||||||
|
@ -121,8 +117,8 @@ impl State {
|
||||||
self.selected_mod_index = Some(index);
|
self.selected_mod_index = Some(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_mod(&mut self, info: ModInfo) {
|
pub fn add_mod(&mut self, info: Arc<ModInfo>) {
|
||||||
if let Some(pos) = self.mods.index_of(&info) {
|
if let Some(pos) = self.mods.iter().position(|i| i.id == info.id) {
|
||||||
self.mods.set(pos, info);
|
self.mods.set(pos, info);
|
||||||
self.selected_mod_index = Some(pos);
|
self.selected_mod_index = Some(pos);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use druid::{
|
use druid::{
|
||||||
AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse, Target,
|
AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse, Target,
|
||||||
};
|
};
|
||||||
|
@ -9,9 +11,9 @@ pub(crate) const ACTION_SELECT_MOD: Selector<usize> = Selector::new("dtmm.action
|
||||||
pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
|
pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
|
||||||
pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector =
|
pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector =
|
||||||
Selector::new("dtmm.action.selected-mod-down");
|
Selector::new("dtmm.action.selected-mod-down");
|
||||||
pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
|
pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector<SingleUse<Arc<ModInfo>>> =
|
||||||
Selector::new("dtmm.action.srart-delete-selected-mod");
|
Selector::new("dtmm.action.srart-delete-selected-mod");
|
||||||
pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
|
pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector<SingleUse<Arc<ModInfo>>> =
|
||||||
Selector::new("dtmm.action.finish-delete-selected-mod");
|
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_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy");
|
||||||
|
@ -23,7 +25,7 @@ pub(crate) const ACTION_FINISH_RESET_DEPLOYMENT: Selector =
|
||||||
Selector::new("dtmm.action.finish-reset-deployment");
|
Selector::new("dtmm.action.finish-reset-deployment");
|
||||||
|
|
||||||
pub(crate) const ACTION_ADD_MOD: Selector<FileInfo> = Selector::new("dtmm.action.add-mod");
|
pub(crate) const ACTION_ADD_MOD: Selector<FileInfo> = Selector::new("dtmm.action.add-mod");
|
||||||
pub(crate) const ACTION_FINISH_ADD_MOD: Selector<SingleUse<ModInfo>> =
|
pub(crate) const ACTION_FINISH_ADD_MOD: Selector<SingleUse<Arc<ModInfo>>> =
|
||||||
Selector::new("dtmm.action.finish-add-mod");
|
Selector::new("dtmm.action.finish-add-mod");
|
||||||
|
|
||||||
pub(crate) const ACTION_LOG: Selector<SingleUse<String>> = Selector::new("dtmm.action.log");
|
pub(crate) const ACTION_LOG: Selector<SingleUse<String>> = Selector::new("dtmm.action.log");
|
||||||
|
@ -33,11 +35,13 @@ pub(crate) const ACTION_START_SAVE_SETTINGS: Selector =
|
||||||
pub(crate) const ACTION_FINISH_SAVE_SETTINGS: Selector =
|
pub(crate) const ACTION_FINISH_SAVE_SETTINGS: Selector =
|
||||||
Selector::new("dtmm.action.finish-save-settings");
|
Selector::new("dtmm.action.finish-save-settings");
|
||||||
|
|
||||||
|
pub(crate) const ACTION_SET_DIRTY: Selector = Selector::new("dtmm.action.set-dirty");
|
||||||
|
|
||||||
pub(crate) enum AsyncAction {
|
pub(crate) enum AsyncAction {
|
||||||
DeployMods(State),
|
DeployMods(State),
|
||||||
ResetDeployment(State),
|
ResetDeployment(State),
|
||||||
AddMod((State, FileInfo)),
|
AddMod((State, FileInfo)),
|
||||||
DeleteMod((State, ModInfo)),
|
DeleteMod((State, Arc<ModInfo>)),
|
||||||
SaveSettings(State),
|
SaveSettings(State),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +85,7 @@ impl AppDelegate<State> for Delegate {
|
||||||
}
|
}
|
||||||
cmd if cmd.is(ACTION_FINISH_DEPLOY) => {
|
cmd if cmd.is(ACTION_FINISH_DEPLOY) => {
|
||||||
state.is_deployment_in_progress = false;
|
state.is_deployment_in_progress = false;
|
||||||
|
state.dirty = false;
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
}
|
}
|
||||||
cmd if cmd.is(ACTION_START_RESET_DEPLOYMENT) => {
|
cmd if cmd.is(ACTION_START_RESET_DEPLOYMENT) => {
|
||||||
|
@ -165,7 +170,6 @@ impl AppDelegate<State> for Delegate {
|
||||||
};
|
};
|
||||||
|
|
||||||
state.mods.remove(index);
|
state.mods.remove(index);
|
||||||
// ctx.submit_command(ACTION_START_SAVE_SETTINGS);
|
|
||||||
|
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
}
|
}
|
||||||
|
@ -188,7 +192,6 @@ impl AppDelegate<State> for Delegate {
|
||||||
.expect("command type matched but didn't contain the expected value");
|
.expect("command type matched but didn't contain the expected value");
|
||||||
if let Some(info) = info.take() {
|
if let Some(info) = info.take() {
|
||||||
state.add_mod(info);
|
state.add_mod(info);
|
||||||
// ctx.submit_command(ACTION_START_SAVE_SETTINGS);
|
|
||||||
}
|
}
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
}
|
}
|
||||||
|
@ -226,6 +229,10 @@ impl AppDelegate<State> for Delegate {
|
||||||
|
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
}
|
}
|
||||||
|
cmd if cmd.is(ACTION_SET_DIRTY) => {
|
||||||
|
state.dirty = true;
|
||||||
|
Handled::Yes
|
||||||
|
}
|
||||||
cmd => {
|
cmd => {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
tracing::warn!("Unknown command: {:?}", cmd);
|
tracing::warn!("Unknown command: {:?}", cmd);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use druid::im::Vector;
|
use druid::im::Vector;
|
||||||
use druid::{Data, Lens};
|
use druid::{Data, Lens};
|
||||||
|
|
||||||
|
@ -5,9 +7,9 @@ use super::{ModInfo, State};
|
||||||
|
|
||||||
pub(crate) struct SelectedModLens;
|
pub(crate) struct SelectedModLens;
|
||||||
|
|
||||||
impl Lens<State, Option<ModInfo>> for SelectedModLens {
|
impl Lens<State, Option<Arc<ModInfo>>> for SelectedModLens {
|
||||||
#[tracing::instrument(name = "SelectedModLens::with", skip_all)]
|
#[tracing::instrument(name = "SelectedModLens::with", skip_all)]
|
||||||
fn with<V, F: FnOnce(&Option<ModInfo>) -> V>(&self, data: &State, f: F) -> V {
|
fn with<V, F: FnOnce(&Option<Arc<ModInfo>>) -> V>(&self, data: &State, f: F) -> V {
|
||||||
let info = data
|
let info = data
|
||||||
.selected_mod_index
|
.selected_mod_index
|
||||||
.and_then(|i| data.mods.get(i).cloned());
|
.and_then(|i| data.mods.get(i).cloned());
|
||||||
|
@ -16,16 +18,16 @@ impl Lens<State, Option<ModInfo>> for SelectedModLens {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(name = "SelectedModLens::with_mut", skip_all)]
|
#[tracing::instrument(name = "SelectedModLens::with_mut", skip_all)]
|
||||||
fn with_mut<V, F: FnOnce(&mut Option<ModInfo>) -> V>(&self, data: &mut State, f: F) -> V {
|
fn with_mut<V, F: FnOnce(&mut Option<Arc<ModInfo>>) -> V>(&self, data: &mut State, f: F) -> V {
|
||||||
match data.selected_mod_index {
|
match data.selected_mod_index {
|
||||||
Some(i) => {
|
Some(i) => {
|
||||||
let mut info = data.mods.get_mut(i).cloned();
|
let mut info = data.mods.get_mut(i).cloned();
|
||||||
let ret = f(&mut info);
|
let ret = f(&mut info);
|
||||||
|
|
||||||
if let Some(info) = info {
|
if let Some(new) = info {
|
||||||
// TODO: Figure out a way to check for equality and
|
// TODO: Figure out a way to check for equality and
|
||||||
// only update when needed
|
// only update when needed
|
||||||
data.mods.set(i, info);
|
data.mods.set(i, new);
|
||||||
} else {
|
} else {
|
||||||
data.selected_mod_index = None;
|
data.selected_mod_index = None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use druid::widget::{Button, Controller, Scroll};
|
use druid::widget::{Button, Controller, Scroll};
|
||||||
use druid::{Data, Env, Event, EventCtx, Rect, UpdateCtx, Widget};
|
use druid::{Data, Env, Event, EventCtx, Rect, UpdateCtx, Widget};
|
||||||
|
|
||||||
use crate::state::{State, ACTION_START_SAVE_SETTINGS};
|
use crate::state::{State, ACTION_SET_DIRTY, ACTION_START_SAVE_SETTINGS};
|
||||||
|
|
||||||
pub struct DisabledButtonController;
|
pub struct DisabledButtonController;
|
||||||
|
|
||||||
|
@ -57,11 +57,16 @@ impl<T: Data, W: Widget<T>> Controller<T, Scroll<T, W>> for AutoScrollController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A controller that submits the command to save settings every time its widget's
|
macro_rules! compare_state_fields {
|
||||||
/// data changes.
|
($old:ident, $new:ident, $($field:ident),+) => {
|
||||||
pub struct SaveSettingsController;
|
$($old.$field != $new.$field) || +
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<W: Widget<State>> Controller<State, W> for SaveSettingsController {
|
/// A controller that tracks state changes for certain fields and submits commands to handle them.
|
||||||
|
pub struct DirtyStateController;
|
||||||
|
|
||||||
|
impl<W: Widget<State>> Controller<State, W> for DirtyStateController {
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
child: &mut W,
|
child: &mut W,
|
||||||
|
@ -70,13 +75,14 @@ impl<W: Widget<State>> Controller<State, W> for SaveSettingsController {
|
||||||
data: &State,
|
data: &State,
|
||||||
env: &Env,
|
env: &Env,
|
||||||
) {
|
) {
|
||||||
// Only filter for the values that actually go into the settings file.
|
if compare_state_fields!(old_data, data, mods, game_dir, data_dir) {
|
||||||
if old_data.mods != data.mods
|
|
||||||
|| old_data.game_dir != data.game_dir
|
|
||||||
|| old_data.data_dir != data.data_dir
|
|
||||||
{
|
|
||||||
ctx.submit_command(ACTION_START_SAVE_SETTINGS);
|
ctx.submit_command(ACTION_START_SAVE_SETTINGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if compare_state_fields!(old_data, data, mods, game_dir) {
|
||||||
|
ctx.submit_command(ACTION_SET_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
child.update(ctx, old_data, data, env)
|
child.update(ctx, old_data, data, env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use druid::im::Vector;
|
use druid::im::Vector;
|
||||||
use druid::lens;
|
use druid::lens;
|
||||||
use druid::widget::{
|
use druid::widget::{
|
||||||
|
@ -15,7 +17,7 @@ use crate::state::{
|
||||||
ACTION_START_RESET_DEPLOYMENT,
|
ACTION_START_RESET_DEPLOYMENT,
|
||||||
};
|
};
|
||||||
use crate::ui::theme;
|
use crate::ui::theme;
|
||||||
use crate::ui::widget::controller::{AutoScrollController, SaveSettingsController};
|
use crate::ui::widget::controller::{AutoScrollController, DirtyStateController};
|
||||||
use crate::ui::widget::PathBufFormatter;
|
use crate::ui::widget::PathBufFormatter;
|
||||||
|
|
||||||
const TITLE: &str = "Darktide Mod Manager";
|
const TITLE: &str = "Darktide Mod Manager";
|
||||||
|
@ -31,43 +33,48 @@ pub(crate) fn new() -> WindowDesc<State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_top_bar() -> impl Widget<State> {
|
fn build_top_bar() -> impl Widget<State> {
|
||||||
|
let mods_button = Button::new("Mods")
|
||||||
|
.on_click(|_ctx, state: &mut State, _env| state.current_view = View::Mods);
|
||||||
|
|
||||||
|
let settings_button = Button::new("Settings").on_click(|_ctx, state: &mut State, _env| {
|
||||||
|
state.current_view = View::Settings;
|
||||||
|
});
|
||||||
|
|
||||||
|
let deploy_button = {
|
||||||
|
Button::dynamic(|state: &State, _| {
|
||||||
|
let mut s = String::new();
|
||||||
|
if state.dirty {
|
||||||
|
s.push_str("! ");
|
||||||
|
}
|
||||||
|
s.push_str("Deploy Mods");
|
||||||
|
s
|
||||||
|
})
|
||||||
|
.on_click(|ctx, _state: &mut State, _env| {
|
||||||
|
ctx.submit_command(ACTION_START_DEPLOY);
|
||||||
|
})
|
||||||
|
.disabled_if(|data, _| data.is_deployment_in_progress || data.is_reset_in_progress)
|
||||||
|
};
|
||||||
|
|
||||||
|
let reset_button = Button::new("Reset Game")
|
||||||
|
.on_click(|ctx, _state: &mut State, _env| {
|
||||||
|
ctx.submit_command(ACTION_START_RESET_DEPLOYMENT);
|
||||||
|
})
|
||||||
|
.disabled_if(|data, _| data.is_deployment_in_progress || data.is_reset_in_progress);
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.must_fill_main_axis(true)
|
.must_fill_main_axis(true)
|
||||||
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(mods_button)
|
||||||
Button::new("Mods")
|
|
||||||
.on_click(|_ctx, state: &mut State, _env| state.current_view = View::Mods),
|
|
||||||
)
|
|
||||||
.with_default_spacer()
|
.with_default_spacer()
|
||||||
.with_child(
|
.with_child(settings_button),
|
||||||
Button::new("Settings").on_click(|_ctx, state: &mut State, _env| {
|
|
||||||
state.current_view = View::Settings;
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(deploy_button)
|
||||||
Button::new("Deploy Mods")
|
|
||||||
.on_click(|ctx, _state: &mut State, _env| {
|
|
||||||
ctx.submit_command(ACTION_START_DEPLOY);
|
|
||||||
})
|
|
||||||
.disabled_if(|data, _| {
|
|
||||||
data.is_deployment_in_progress || data.is_reset_in_progress
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.with_default_spacer()
|
.with_default_spacer()
|
||||||
.with_child(
|
.with_child(reset_button),
|
||||||
Button::new("Reset Game")
|
|
||||||
.on_click(|ctx, _state: &mut State, _env| {
|
|
||||||
ctx.submit_command(ACTION_START_RESET_DEPLOYMENT);
|
|
||||||
})
|
|
||||||
.disabled_if(|data, _| {
|
|
||||||
data.is_deployment_in_progress || data.is_reset_in_progress
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.padding(theme::TOP_BAR_INSETS)
|
.padding(theme::TOP_BAR_INSETS)
|
||||||
.background(theme::TOP_BAR_BACKGROUND_COLOR)
|
.background(theme::TOP_BAR_BACKGROUND_COLOR)
|
||||||
|
@ -77,9 +84,11 @@ fn build_top_bar() -> impl Widget<State> {
|
||||||
|
|
||||||
fn build_mod_list() -> impl Widget<State> {
|
fn build_mod_list() -> impl Widget<State> {
|
||||||
let list = List::new(|| {
|
let list = List::new(|| {
|
||||||
let checkbox =
|
let checkbox = Checkbox::new("")
|
||||||
Checkbox::new("").lens(lens!((usize, ModInfo, bool), 1).then(ModInfo::enabled));
|
.lens(lens!((usize, Arc<ModInfo>, bool), 1).then(ModInfo::enabled.in_arc()));
|
||||||
let name = Label::raw().lens(lens!((usize, ModInfo, bool), 1).then(ModInfo::name));
|
|
||||||
|
let name =
|
||||||
|
Label::raw().lens(lens!((usize, Arc<ModInfo>, bool), 1).then(ModInfo::name.in_arc()));
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.must_fill_main_axis(true)
|
.must_fill_main_axis(true)
|
||||||
|
@ -109,8 +118,10 @@ fn build_mod_list() -> impl Widget<State> {
|
||||||
.collect::<Vector<_>>()
|
.collect::<Vector<_>>()
|
||||||
},
|
},
|
||||||
|state, infos| {
|
|state, infos| {
|
||||||
infos.into_iter().for_each(|(i, info, _)| {
|
infos.into_iter().for_each(|(i, new, _)| {
|
||||||
state.mods.set(i, info);
|
if state.mods.get(i).cloned() != Some(new.clone()) {
|
||||||
|
state.mods.set(i, new);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -142,12 +153,12 @@ fn build_mod_details_buttons() -> impl Widget<State> {
|
||||||
.on_click(|_ctx, enabled: &mut bool, _env| {
|
.on_click(|_ctx, enabled: &mut bool, _env| {
|
||||||
*enabled = !(*enabled);
|
*enabled = !(*enabled);
|
||||||
})
|
})
|
||||||
.lens(ModInfo::enabled)
|
.lens(ModInfo::enabled.in_arc())
|
||||||
},
|
},
|
||||||
// TODO: Gray out
|
// TODO: Gray out
|
||||||
|| Button::new("Enable Mod"),
|
|| Button::new("Enable Mod"),
|
||||||
)
|
)
|
||||||
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
|
.disabled_if(|info: &Option<Arc<ModInfo>>, _env: &druid::Env| info.is_none())
|
||||||
.lens(State::selected_mod);
|
.lens(State::selected_mod);
|
||||||
|
|
||||||
let button_add_mod = Button::new("Add Mod").on_click(|ctx, _state: &mut State, _env| {
|
let button_add_mod = Button::new("Add Mod").on_click(|ctx, _state: &mut State, _env| {
|
||||||
|
@ -162,14 +173,14 @@ fn build_mod_details_buttons() -> impl Widget<State> {
|
||||||
});
|
});
|
||||||
|
|
||||||
let button_delete_mod = Button::new("Delete Mod")
|
let button_delete_mod = Button::new("Delete Mod")
|
||||||
.on_click(|ctx, data: &mut Option<ModInfo>, _env| {
|
.on_click(|ctx, data: &mut Option<Arc<ModInfo>>, _env| {
|
||||||
if let Some(info) = data {
|
if let Some(info) = data {
|
||||||
ctx.submit_command(
|
ctx.submit_command(
|
||||||
ACTION_START_DELETE_SELECTED_MOD.with(SingleUse::new(info.clone())),
|
ACTION_START_DELETE_SELECTED_MOD.with(SingleUse::new(info.clone())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
|
.disabled_if(|info: &Option<Arc<ModInfo>>, _env: &druid::Env| info.is_none())
|
||||||
.lens(State::selected_mod);
|
.lens(State::selected_mod);
|
||||||
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
|
@ -203,10 +214,10 @@ fn build_mod_details_info() -> impl Widget<State> {
|
||||||
// Force the label to take up the entire details' pane width,
|
// Force the label to take up the entire details' pane width,
|
||||||
// so that we can center-align it.
|
// so that we can center-align it.
|
||||||
.expand_width()
|
.expand_width()
|
||||||
.lens(ModInfo::name);
|
.lens(ModInfo::name.in_arc());
|
||||||
let description = Label::raw()
|
let description = Label::raw()
|
||||||
.with_line_break_mode(LineBreaking::WordWrap)
|
.with_line_break_mode(LineBreaking::WordWrap)
|
||||||
.lens(ModInfo::description);
|
.lens(ModInfo::description.in_arc());
|
||||||
|
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.cross_axis_alignment(CrossAxisAlignment::Start)
|
.cross_axis_alignment(CrossAxisAlignment::Start)
|
||||||
|
@ -312,5 +323,5 @@ fn build_window() -> impl Widget<State> {
|
||||||
.with_child(build_top_bar())
|
.with_child(build_top_bar())
|
||||||
.with_flex_child(build_main(), 1.0)
|
.with_flex_child(build_main(), 1.0)
|
||||||
.with_child(build_log_view())
|
.with_child(build_log_view())
|
||||||
.controller(SaveSettingsController)
|
.controller(DirtyStateController)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
use clap::{parser::ValueSource, ArgMatches};
|
use clap::{parser::ValueSource, ArgMatches};
|
||||||
|
@ -38,6 +39,7 @@ impl<'a> From<&'a State> for ConfigSerialize<'a> {
|
||||||
mod_order: state
|
mod_order: state
|
||||||
.mods
|
.mods
|
||||||
.iter()
|
.iter()
|
||||||
|
.map(Arc::as_ref)
|
||||||
.map(LoadOrderEntrySerialize::from)
|
.map(LoadOrderEntrySerialize::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue