This commit is contained in:
Lucas Schwiderski 2023-01-18 00:32:31 +01:00
parent 50569a3c56
commit a25a6b2917
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
7 changed files with 356 additions and 65 deletions

View file

@ -0,0 +1,47 @@
use druid::widget::{Button, Controller};
use druid::{Data, Env, Event, EventCtx, LifeCycle, LifeCycleCtx, UpdateCtx, Widget};
pub struct DisabledButtonController;
impl<T: Data> Controller<T, Button<T>> for DisabledButtonController {
fn event(
&mut self,
child: &mut Button<T>,
ctx: &mut EventCtx,
event: &Event,
data: &mut T,
env: &Env,
) {
if !ctx.is_disabled() {
ctx.set_disabled(true);
ctx.request_paint();
}
child.event(ctx, event, data, env)
}
fn lifecycle(
&mut self,
child: &mut Button<T>,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &T,
env: &Env,
) {
child.lifecycle(ctx, event, data, env)
}
fn update(
&mut self,
child: &mut Button<T>,
ctx: &mut UpdateCtx,
old_data: &T,
data: &T,
env: &Env,
) {
if !ctx.is_disabled() {
ctx.set_disabled(true);
ctx.request_paint();
}
child.update(ctx, old_data, data, env)
}
}

View file

@ -1,3 +1,5 @@
#![feature(let_chains)]
use clap::command;
use color_eyre::Report;
use color_eyre::Result;
@ -8,6 +10,7 @@ use tracing_subscriber::EnvFilter;
use crate::state::State;
mod controller;
mod main_window;
mod state;
mod theme;

View file

@ -5,19 +5,21 @@ use druid::widget::{
};
use druid::{lens, Insets, LensExt, Widget, WidgetExt, WindowDesc};
use crate::state::{ModInfo, State, View};
use crate::state::{
ModInfo, State, StateController, View, ACTION_DELETE_SELECTED_MOD, ACTION_SELECTED_MOD_DOWN,
ACTION_SELECTED_MOD_UP, ACTION_SELECT_MOD,
};
use crate::theme;
use crate::widget::ExtraWidgetExt;
const TITLE: &str = "Darktide Mod Manager";
const WINDOW_WIDTH: f64 = 800.0;
const WINDOW_HEIGHT: f64 = 600.0;
const WINDOW_SIZE: (f64, f64) = (800.0, 600.0);
const MOD_DETAILS_MIN_WIDTH: f64 = 325.0;
pub(crate) fn new() -> WindowDesc<State> {
WindowDesc::new(build_window())
.title(TITLE)
.window_size((WINDOW_WIDTH, WINDOW_HEIGHT))
.window_size(WINDOW_SIZE)
}
fn build_top_bar() -> impl Widget<State> {
@ -33,9 +35,11 @@ fn build_top_bar() -> impl Widget<State> {
)
.with_default_spacer()
.with_child(
Button::new("Settings").on_click(|_ctx, state: &mut State, _env| {
Button::new("Settings")
.on_click(|_ctx, state: &mut State, _env| {
state.set_current_view(View::Settings)
}),
})
.hidden_if(|_, _| true),
)
.with_default_spacer()
.with_child(
@ -68,32 +72,21 @@ fn build_mod_list() -> impl Widget<State> {
let list = List::new(|| {
Flex::row()
.must_fill_main_axis(true)
// .with_child(
// Label::dynamic(|enabled, _env| {
// if *enabled {
// "Enabled".into()
// } else {
// "Disabled".into()
// }
// })
// .lens(
// lens::Identity
// .map(
// |(i, info)| info,
// |(i, info), new_info| {
// todo!();
// },
// )
// .then(ModInfo::enabled),
// ),
// )
// .with_child(Label::raw().lens(ModInfo::name))
.on_click(|_ctx, state, _env| {
todo!();
.with_child(
Label::dynamic(|enabled, _env| {
if *enabled {
"Enabled".into()
} else {
"Disabled".into()
}
})
.lens(lens!((usize, ModInfo), 1).then(ModInfo::enabled)),
)
.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)))
});
Scroll::new(list)
let scroll = Scroll::new(list)
.vertical()
.lens(State::mods.map(
|mods| {
@ -108,7 +101,12 @@ fn build_mod_list() -> impl Widget<State> {
});
},
))
.content_must_fill()
.content_must_fill();
Flex::column()
.must_fill_main_axis(true)
.with_child(Flex::row())
.with_flex_child(scroll, 1.0)
}
fn build_mod_details() -> impl Widget<State> {
@ -121,23 +119,16 @@ fn build_mod_details() -> impl Widget<State> {
},
Flex::column,
)
.padding(Insets::uniform_xy(5.0, 1.0))
.lens(State::selected_mod);
let button_move_up = Button::new("Move Up")
.on_click(|_ctx, index: &mut Option<usize>, _env| {
if let Some(i) = index.as_mut() {
*i = i.saturating_sub(1)
}
})
.lens(State::selected_mod_index);
.on_click(|ctx, _state, _env| ctx.submit_notification(ACTION_SELECTED_MOD_UP))
.disabled_if(|state: &State, _env: &druid::Env| state.can_move_mod_up());
let button_move_down = Button::new("Move Down")
.on_click(|_ctx, index: &mut Option<usize>, _env| {
if let Some(i) = index.as_mut() {
*i = i.saturating_add(1)
}
})
.lens(State::selected_mod_index);
.on_click(|ctx, _state, _env| ctx.submit_notification(ACTION_SELECTED_MOD_DOWN))
.disabled_if(|state: &State, _env: &druid::Env| state.can_move_mod_down());
let button_toggle_mod = Maybe::new(
|| {
@ -145,17 +136,18 @@ fn build_mod_details() -> impl Widget<State> {
if *enabled {
"Disable Mod".into()
} else {
"Enabled Mod".into()
"Enable Mod".into()
}
})
.on_click(|_ctx, info: &mut bool, _env| {
*info = !*info;
.on_click(|_ctx, enabled: &mut bool, _env| {
*enabled = !(*enabled);
})
.lens(ModInfo::enabled)
},
// TODO: Gray out
|| Button::new("Enable Mod"),
)
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
.lens(State::selected_mod);
let button_add_mod = Button::new("Add Mod").on_click(|_ctx, state: &mut State, _env| {
@ -165,7 +157,9 @@ fn build_mod_details() -> impl Widget<State> {
});
let button_delete_mod = Button::new("Delete Mod")
.on_click(|_ctx, data: &mut State, _env| data.delete_selected_mod());
.on_click(|ctx, _state, _env| ctx.submit_notification(ACTION_DELETE_SELECTED_MOD))
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
.lens(State::selected_mod);
let buttons = Flex::column()
.with_child(
@ -234,4 +228,5 @@ fn build_window() -> impl Widget<State> {
.must_fill_main_axis(true)
.with_child(build_top_bar())
.with_flex_child(build_main(), 1.0)
.controller(StateController::new())
}

View file

@ -1,7 +1,13 @@
use std::sync::Arc;
use druid::im::Vector;
use druid::{Data, Lens};
use druid::widget::Controller;
use druid::{Data, Env, Event, EventCtx, Lens, Selector, Widget};
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_DOWN: Selector = Selector::new("dtmm.action.selected-mod-down");
pub const ACTION_DELETE_SELECTED_MOD: Selector = Selector::new("dtmm.action.delete-selected-mod");
#[derive(Copy, Clone, Data, PartialEq)]
pub(crate) enum View {
@ -48,6 +54,7 @@ pub(crate) struct State {
pub(crate) struct SelectedModLens;
impl Lens<State, Option<ModInfo>> for SelectedModLens {
#[tracing::instrument(name = "SelectedModLens::with", skip_all)]
fn with<V, F: FnOnce(&Option<ModInfo>) -> V>(&self, data: &State, f: F) -> V {
let info = data
.selected_mod_index
@ -56,11 +63,25 @@ impl Lens<State, Option<ModInfo>> for SelectedModLens {
f(&info)
}
#[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 {
let mut info = data
.selected_mod_index
.and_then(|i| data.mods.get_mut(i).cloned());
f(&mut info)
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),
}
}
}
@ -70,21 +91,33 @@ impl Lens<State, Option<ModInfo>> for SelectedModLens {
pub(crate) struct IndexedVectorLens;
impl<T: Data> Lens<Vector<T>, Vector<(usize, T)>> for IndexedVectorLens {
fn with<V, F: FnOnce(&Vector<(usize, T)>) -> V>(&self, data: &Vector<T>, f: F) -> V {
let data = data
#[tracing::instrument(name = "IndexedVectorLens::with", skip_all)]
fn with<V, F: FnOnce(&Vector<(usize, T)>) -> V>(&self, values: &Vector<T>, f: F) -> V {
let indexed = values
.iter()
.enumerate()
.map(|(i, val)| (i, val.clone()))
.collect();
f(&data)
f(&indexed)
}
#[tracing::instrument(name = "IndexedVectorLens::with_mut", skip_all)]
fn with_mut<V, F: FnOnce(&mut Vector<(usize, T)>) -> V>(
&self,
data: &mut Vector<T>,
values: &mut Vector<T>,
f: F,
) -> V {
todo!()
let mut indexed = values
.iter()
.enumerate()
.map(|(i, val)| (i, val.clone()))
.collect();
let ret = f(&mut indexed);
tracing::trace!("with_mut: {}", indexed.len());
*values = indexed.into_iter().map(|(_i, val)| val).collect();
ret
}
}
@ -104,16 +137,90 @@ impl State {
self.current_view = view;
}
pub fn delete_selected_mod(&mut self) {
let Some(index) = self.selected_mod_index else {
return;
};
self.mods.remove(index);
pub fn select_mod(&mut self, index: usize) {
self.selected_mod_index = Some(index);
}
pub fn add_mod(&mut self, info: ModInfo) {
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(true)
}
pub fn can_move_mod_up(&self) -> bool {
self.selected_mod_index.map(|i| i == 0).unwrap_or(true)
}
}
pub struct StateController {}
impl StateController {
pub fn new() -> Self {
Self {}
}
}
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),
}
}
}

View file

@ -0,0 +1,60 @@
use druid::widget::prelude::*;
use druid::{Point, WidgetPod};
pub struct HiddenIf<T, W> {
child: WidgetPod<T, W>,
hidden_if: Box<dyn Fn(&T, &Env) -> bool>,
}
impl<T: Data, W: Widget<T>> HiddenIf<T, W> {
pub fn new(child: W, hidden_if: impl Fn(&T, &Env) -> bool + 'static) -> Self {
Self {
hidden_if: Box::new(hidden_if),
child: WidgetPod::new(child),
}
}
}
impl<T: Data, W: Widget<T>> Widget<T> for HiddenIf<T, W> {
#[tracing::instrument(name = "HideContainer", level = "trace", skip_all)]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let hidden = (self.hidden_if)(data, env);
ctx.set_disabled(hidden);
self.child.event(ctx, event, data, env);
}
#[tracing::instrument(name = "HideContainer", level = "trace", skip_all)]
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
let hidden = (self.hidden_if)(data, env);
ctx.set_disabled(hidden);
self.child.lifecycle(ctx, event, data, env)
}
#[tracing::instrument(name = "HideContainer", level = "trace", skip_all)]
fn update(&mut self, ctx: &mut UpdateCtx, _: &T, data: &T, env: &Env) {
self.child.update(ctx, data, env);
}
#[tracing::instrument(name = "HideContainer", level = "trace", skip_all)]
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
bc.debug_check("HideContainer");
let hidden = (self.hidden_if)(data, env);
if hidden {
return Size::ZERO;
}
let child_size = self.child.layout(ctx, bc, data, env);
self.child.set_origin(ctx, Point::new(0.0, 0.0));
child_size
}
#[tracing::instrument(name = "HideContainer", level = "trace", skip_all)]
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
let hidden = (self.hidden_if)(data, env);
if hidden {
return;
}
self.child.paint(ctx, data, env);
}
}

View file

@ -1,14 +1,20 @@
use druid::{Data, Widget};
use druid::{Data, Env, Widget};
use self::fill_container::FillContainer;
use self::hidden_if::HiddenIf;
pub mod container;
pub mod fill_container;
pub mod hidden_if;
pub trait ExtraWidgetExt<T: Data>: Widget<T> + Sized + 'static {
fn content_must_fill(self) -> FillContainer<T> {
FillContainer::new(self)
}
fn hidden_if(self, hidden_if: impl Fn(&T, &Env) -> bool + 'static) -> HiddenIf<T, Self> {
HiddenIf::new(self, hidden_if)
}
}
impl<T: Data, W: Widget<T> + 'static> ExtraWidgetExt<T> for W {}

View file

@ -0,0 +1,73 @@
use druid::widget::{Controller, Flex};
use druid::{Data, Widget};
pub struct TableSelect<T> {
widget: Flex<T>,
controller: TableSelectController<T>,
}
impl<T: Data> TableSelect<T> {
pub fn new(values: impl IntoIterator<Item = (impl Widget<T> + 'static)>) -> Self {
todo!();
}
}
impl<T: Data> Widget<T> for TableSelect<T> {
fn event(
&mut self,
ctx: &mut druid::EventCtx,
event: &druid::Event,
data: &mut T,
env: &druid::Env,
) {
todo!()
}
fn lifecycle(
&mut self,
ctx: &mut druid::LifeCycleCtx,
event: &druid::LifeCycle,
data: &T,
env: &druid::Env,
) {
todo!()
}
fn update(&mut self, ctx: &mut druid::UpdateCtx, old_data: &T, data: &T, env: &druid::Env) {
todo!()
}
fn layout(
&mut self,
ctx: &mut druid::LayoutCtx,
bc: &druid::BoxConstraints,
data: &T,
env: &druid::Env,
) -> druid::Size {
todo!()
}
fn paint(&mut self, ctx: &mut druid::PaintCtx, data: &T, env: &druid::Env) {
todo!()
}
}
struct TableSelectController<T> {
inner: T,
}
impl<T: Data> TableSelectController<T> {}
impl<T: Data> Controller<T, Flex<T>> for TableSelectController<T> {}
pub struct TableItem<T> {
inner: dyn Widget<T>,
}
impl<T: Data> TableItem<T> {
pub fn new(inner: impl Widget<T>) -> Self {
todo!();
}
}
impl<T: Data> Widget<T> for TableItem<T> {}