Darktide Mod Manager #39

Merged
lucas merged 91 commits from feat/dtmm into master 2023-03-01 22:27:42 +01:00
3 changed files with 90 additions and 110 deletions
Showing only changes of commit 4b7f12e487 - Show all commits

View file

@ -18,7 +18,7 @@ use tracing_subscriber::prelude::*;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use crate::engine::deploy_mods; use crate::engine::deploy_mods;
use crate::state::{AsyncAction, Delegate, State, COMMAND_FINISH_DEPLOY}; use crate::state::{AsyncAction, Delegate, State, ACTION_FINISH_DEPLOY};
mod controller; mod controller;
mod engine; mod engine;
@ -45,7 +45,7 @@ fn work_thread(
event_sink event_sink
.write() .write()
.await .await
.submit_command(COMMAND_FINISH_DEPLOY, (), Target::Auto) .submit_command(ACTION_FINISH_DEPLOY, (), Target::Auto)
.expect("failed to send command"); .expect("failed to send command");
}), }),
}; };

View file

@ -5,9 +5,10 @@ use druid::widget::{
}; };
use druid::{lens, Insets, LensExt, Widget, WidgetExt, WindowDesc}; use druid::{lens, Insets, LensExt, Widget, WidgetExt, WindowDesc};
use crate::state::{ModInfo, PathBufFormatter, State, View, ACTION_ADD_MOD};
use crate::state::{ use crate::state::{
ModInfo, PathBufFormatter, State, StateController, View, ACTION_DELETE_SELECTED_MOD, ACTION_DELETE_SELECTED_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP,
ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP, ACTION_SELECT_MOD, COMMAND_START_DEPLOY, ACTION_SELECT_MOD, ACTION_START_DEPLOY,
}; };
use crate::theme; use crate::theme;
use crate::widget::ExtraWidgetExt; use crate::widget::ExtraWidgetExt;
@ -51,7 +52,7 @@ fn build_top_bar() -> impl Widget<State> {
.with_child( .with_child(
Button::new("Deploy Mods") Button::new("Deploy Mods")
.on_click(|ctx, _state: &mut State, _env| { .on_click(|ctx, _state: &mut State, _env| {
ctx.submit_command(COMMAND_START_DEPLOY); ctx.submit_command(ACTION_START_DEPLOY);
}) })
.disabled_if(|data, _| !data.can_deploy_mods()), .disabled_if(|data, _| !data.can_deploy_mods()),
) )
@ -83,7 +84,7 @@ fn build_mod_list() -> impl Widget<State> {
.lens(lens!((usize, ModInfo), 1).then(ModInfo::enabled)), .lens(lens!((usize, ModInfo), 1).then(ModInfo::enabled)),
) )
.with_child(Label::raw().lens(lens!((usize, ModInfo), 1).then(ModInfo::name))) .with_child(Label::raw().lens(lens!((usize, ModInfo), 1).then(ModInfo::name)))
.on_click(|ctx, (i, _info), _env| ctx.submit_notification(ACTION_SELECT_MOD.with(*i))) .on_click(|ctx, (i, _info), _env| ctx.submit_command(ACTION_SELECT_MOD.with(*i)))
}); });
let scroll = Scroll::new(list) let scroll = Scroll::new(list)
@ -123,11 +124,11 @@ fn build_mod_details() -> impl Widget<State> {
.lens(State::selected_mod); .lens(State::selected_mod);
let button_move_up = Button::new("Move Up") let button_move_up = Button::new("Move Up")
.on_click(|ctx, _state, _env| ctx.submit_notification(ACTION_SELECTED_MOD_UP)) .on_click(|ctx, _state, _env| ctx.submit_command(ACTION_SELECTED_MOD_UP))
.disabled_if(|state: &State, _env: &druid::Env| !state.can_move_mod_up()); .disabled_if(|state: &State, _env: &druid::Env| !state.can_move_mod_up());
let button_move_down = Button::new("Move Down") let button_move_down = Button::new("Move Down")
.on_click(|ctx, _state, _env| ctx.submit_notification(ACTION_SELECTED_MOD_DOWN)) .on_click(|ctx, _state, _env| ctx.submit_command(ACTION_SELECTED_MOD_DOWN))
.disabled_if(|state: &State, _env: &druid::Env| !state.can_move_mod_down()); .disabled_if(|state: &State, _env: &druid::Env| !state.can_move_mod_down());
let button_toggle_mod = Maybe::new( let button_toggle_mod = Maybe::new(
@ -150,14 +151,11 @@ fn build_mod_details() -> impl Widget<State> {
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none()) .disabled_if(|info: &Option<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")
// TODO: Implement properly .on_click(|ctx, _state: &mut State, _env| ctx.submit_command(ACTION_ADD_MOD));
let info = ModInfo::new();
state.add_mod(info);
});
let button_delete_mod = Button::new("Delete Mod") let button_delete_mod = Button::new("Delete Mod")
.on_click(|ctx, _state, _env| ctx.submit_notification(ACTION_DELETE_SELECTED_MOD)) .on_click(|ctx, _state, _env| ctx.submit_command(ACTION_DELETE_SELECTED_MOD))
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none()) .disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
.lens(State::selected_mod); .lens(State::selected_mod);
@ -250,5 +248,4 @@ fn build_window() -> impl Widget<State> {
.must_fill_main_axis(true) .must_fill_main_axis(true)
.with_child(build_top_bar()) .with_child(build_top_bar())
.with_flex_child(build_main(), 1.0) .with_flex_child(build_main(), 1.0)
.controller(StateController::new())
} }

View file

@ -3,20 +3,18 @@ use std::sync::Arc;
use druid::im::Vector; use druid::im::Vector;
use druid::text::Formatter; use druid::text::Formatter;
use druid::widget::Controller; use druid::{AppDelegate, Command, Data, DelegateCtx, Env, Handled, Lens, Selector, Target};
use druid::{
AppDelegate, Command, Data, DelegateCtx, Env, Event, EventCtx, Handled, Lens, Selector, Target,
Widget,
};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
pub const ACTION_SELECT_MOD: Selector<usize> = Selector::new("dtmm.action..select-mod"); pub const ACTION_SELECT_MOD: Selector<usize> = Selector::new("dtmm.action.select-mod");
pub const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up"); pub const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
pub const ACTION_SELECTED_MOD_DOWN: Selector = Selector::new("dtmm.action.selected-mod-down"); pub const ACTION_SELECTED_MOD_DOWN: Selector = Selector::new("dtmm.action.selected-mod-down");
pub const ACTION_DELETE_SELECTED_MOD: Selector = Selector::new("dtmm.action.delete-selected-mod"); pub const ACTION_DELETE_SELECTED_MOD: Selector = Selector::new("dtmm.action.delete-selected-mod");
pub const COMMAND_FINISH_DEPLOY: Selector = Selector::new("dtmm.command.finish-deploy"); pub const ACTION_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy");
pub const COMMAND_START_DEPLOY: Selector = Selector::new("dtmm.command.start-deploy"); pub const ACTION_FINISH_DEPLOY: Selector = Selector::new("dtmm.action.finish-deploy");
pub const ACTION_ADD_MOD: Selector = Selector::new("dtmm.action.add-mod");
#[derive(Copy, Clone, Data, Debug, PartialEq)] #[derive(Copy, Clone, Data, Debug, PartialEq)]
pub(crate) enum View { pub(crate) enum View {
@ -236,75 +234,6 @@ impl<T: Data> Lens<Vector<T>, Vector<(usize, T)>> for IndexedVectorLens {
} }
} }
pub struct StateController {}
impl StateController {
pub fn new() -> Self {
Self {}
}
}
// TODO: Turn notifications into commands on the AppDelegate
impl<W: Widget<State>> Controller<State, W> for StateController {
#[tracing::instrument(name = "StateController::event", skip_all)]
fn event(
&mut self,
child: &mut W,
ctx: &mut EventCtx,
event: &Event,
state: &mut State,
env: &Env,
) {
match event {
Event::Notification(notif) if notif.is(ACTION_SELECT_MOD) => {
ctx.set_handled();
let index = notif
.get(ACTION_SELECT_MOD)
.expect("notification type didn't match after check");
state.select_mod(*index);
}
Event::Notification(notif) if notif.is(ACTION_SELECTED_MOD_UP) => {
ctx.set_handled();
let Some(i) = state.selected_mod_index else {
return;
};
let len = state.mods.len();
if len == 0 || i == 0 {
return;
}
state.mods.swap(i, i - 1);
state.selected_mod_index = Some(i - 1);
}
Event::Notification(notif) if notif.is(ACTION_SELECTED_MOD_DOWN) => {
ctx.set_handled();
let Some(i) = state.selected_mod_index else {
return;
};
let len = state.mods.len();
if len == 0 || i == usize::MAX || i >= len - 1 {
return;
}
state.mods.swap(i, i + 1);
state.selected_mod_index = Some(i + 1);
}
Event::Notification(notif) if notif.is(ACTION_DELETE_SELECTED_MOD) => {
ctx.set_handled();
let Some(index) = state.selected_mod_index else {
return;
};
state.mods.remove(index);
}
_ => child.event(ctx, event, state, env),
}
}
}
pub(crate) enum AsyncAction { pub(crate) enum AsyncAction {
DeployMods(State), DeployMods(State),
} }
@ -326,27 +255,81 @@ impl AppDelegate<State> for Delegate {
_ctx: &mut DelegateCtx, _ctx: &mut DelegateCtx,
_target: Target, _target: Target,
cmd: &Command, cmd: &Command,
data: &mut State, state: &mut State,
_env: &Env, _env: &Env,
) -> Handled { ) -> Handled {
if cmd.is(COMMAND_START_DEPLOY) { match cmd {
if self cmd if cmd.is(ACTION_START_DEPLOY) => {
.sender if self
.send(AsyncAction::DeployMods(data.clone())) .sender
.is_ok() .send(AsyncAction::DeployMods(state.clone()))
{ .is_ok()
data.is_deployment_in_progress = true; {
} else { state.is_deployment_in_progress = true;
tracing::error!("Failed to queue action to deploy mods"); } else {
} tracing::error!("Failed to queue action to deploy mods");
}
Handled::Yes Handled::Yes
} else if cmd.is(COMMAND_FINISH_DEPLOY) { }
data.is_deployment_in_progress = false; cmd if cmd.is(ACTION_START_DEPLOY) => {
Handled::Yes state.is_deployment_in_progress = false;
} else { Handled::Yes
tracing::debug!("Unknown command: {:?}", cmd); }
Handled::No 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_DELETE_SELECTED_MOD) => {
let Some(index) = state.selected_mod_index else {
return Handled::No;
};
state.mods.remove(index);
Handled::Yes
}
cmd if cmd.is(ACTION_ADD_MOD) => {
// TODO: Implement properly
let info = ModInfo::new();
state.add_mod(info);
Handled::Yes
}
cmd => {
tracing::debug!("Unknown command: {:?}", cmd);
Handled::No
}
} }
} }
} }