Darktide Mod Manager #39
3 changed files with 90 additions and 110 deletions
|
@ -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");
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,30 +255,84 @@ 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 {
|
||||||
|
cmd if cmd.is(ACTION_START_DEPLOY) => {
|
||||||
if self
|
if self
|
||||||
.sender
|
.sender
|
||||||
.send(AsyncAction::DeployMods(data.clone()))
|
.send(AsyncAction::DeployMods(state.clone()))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
data.is_deployment_in_progress = true;
|
state.is_deployment_in_progress = true;
|
||||||
} else {
|
} else {
|
||||||
tracing::error!("Failed to queue action to deploy mods");
|
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) => {
|
||||||
|
state.is_deployment_in_progress = false;
|
||||||
Handled::Yes
|
Handled::Yes
|
||||||
} else {
|
}
|
||||||
|
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);
|
tracing::debug!("Unknown command: {:?}", cmd);
|
||||||
Handled::No
|
Handled::No
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct PathBufFormatter;
|
pub(crate) struct PathBufFormatter;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue