feat(dtmm): Add section borders

This implements a new container widget that allows separate widths and
colors for each border side.
This commit is contained in:
Lucas Schwiderski 2023-03-15 16:24:08 +01:00
parent 4c33741b03
commit c38909db22
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
4 changed files with 234 additions and 9 deletions

View file

@ -13,7 +13,10 @@ pub const DISABLED_ALPHA: f64 = 0.65;
pub(crate) fn set_theme_env(env: &mut Env, _: &State) {
env.set(druid::theme::TEXT_COLOR, COLOR_FG);
env.set(druid::theme::SCROLLBAR_COLOR, COLOR_FG);
env.set(druid::theme::BORDER_LIGHT, COLOR_FG);
env.set(druid::theme::BUTTON_BORDER_RADIUS, 2.);
env.set(keys::KEY_BUTTON_BG, COLOR_ACCENT);
env.set(keys::KEY_BUTTON_BG_HOT, COLOR_ACCENT.darken(0.03));
env.set(keys::KEY_BUTTON_BG_ACTIVE, COLOR_ACCENT.darken(0.1));

View file

@ -0,0 +1,197 @@
use druid::kurbo::Line;
use druid::widget::prelude::*;
use druid::{Color, KeyOrValue, Point, WidgetPod};
pub struct Border<T> {
inner: WidgetPod<T, Box<dyn Widget<T>>>,
color: BorderColor,
width: BorderWidths,
// corner_radius: KeyOrValue<RoundedRectRadii>,
}
impl<T: Data> Border<T> {
pub fn new(inner: impl Widget<T> + 'static) -> Self {
let inner = WidgetPod::new(inner).boxed();
Self {
inner,
color: Color::TRANSPARENT.into(),
width: 0f64.into(),
}
}
pub fn set_color(&mut self, color: impl Into<KeyOrValue<Color>>) {
self.color = BorderColor::Uniform(color.into());
}
pub fn with_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
self.set_color(color);
self
}
pub fn set_bottom_border(&mut self, width: impl Into<KeyOrValue<f64>>) {
self.width.bottom = width.into();
}
pub fn with_bottom_border(mut self, width: impl Into<KeyOrValue<f64>>) -> Self {
self.set_bottom_border(width);
self
}
pub fn set_top_border(&mut self, width: impl Into<KeyOrValue<f64>>) {
self.width.top = width.into();
}
pub fn with_top_border(mut self, width: impl Into<KeyOrValue<f64>>) -> Self {
self.set_top_border(width);
self
}
}
impl<T: Data> Widget<T> for Border<T> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
self.inner.event(ctx, event, data, env)
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
self.inner.lifecycle(ctx, event, data, env);
}
fn update(&mut self, ctx: &mut UpdateCtx, _: &T, data: &T, env: &Env) {
self.inner.update(ctx, data, env);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
bc.debug_check("Border");
let (left, top, right, bottom) = self.width.resolve(env);
let inner_bc = bc.shrink((left + right, top + bottom));
let inner_size = self.inner.layout(ctx, &inner_bc, data, env);
let origin = Point::new(left, top);
self.inner.set_origin(ctx, origin);
let size = Size::new(
inner_size.width + left + right,
inner_size.height + top + bottom,
);
let insets = self.inner.compute_parent_paint_insets(size);
ctx.set_paint_insets(insets);
let baseline_offset = self.inner.baseline_offset();
if baseline_offset > 0. {
ctx.set_baseline_offset(baseline_offset + bottom);
}
size
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
let size = ctx.size();
let (left, top, right, bottom) = self.width.resolve(env);
let (col_left, col_top, col_right, col_bottom) = self.color.resolve(env);
self.inner.paint(ctx, data, env);
// There's probably a more elegant way to create the various `Line`s, but this works for now.
// The important bit is to move each line inwards by half each side's border width. Otherwise
// it would draw hald of the border outside of the widget's boundary.
if left > 0. {
ctx.stroke(
Line::new((left / 2., top / 2.), (left / 2., size.height)),
&col_left,
left,
);
}
if top > 0. {
ctx.stroke(
Line::new((left / 2., top / 2.), (size.width - (right / 2.), top / 2.)),
&col_top,
top,
);
}
if right > 0. {
ctx.stroke(
Line::new(
(size.width - (right / 2.), top / 2.),
(size.width - (right / 2.), size.height - (bottom / 2.)),
),
&col_right,
right,
);
}
if bottom > 0. {
ctx.stroke(
Line::new(
(left / 2., size.height - (bottom / 2.)),
(size.width - (right / 2.), size.height - (bottom / 2.)),
),
&col_bottom,
bottom,
);
}
}
}
#[derive(Clone, Debug)]
pub enum BorderColor {
Uniform(KeyOrValue<Color>),
// Individual {
// left: KeyOrValue<Color>,
// top: KeyOrValue<Color>,
// right: KeyOrValue<Color>,
// bottom: KeyOrValue<Color>,
// },
}
impl BorderColor {
pub fn resolve(&self, env: &Env) -> (Color, Color, Color, Color) {
match self {
Self::Uniform(val) => {
let color = val.resolve(env);
(color, color, color, color)
}
}
}
}
impl From<Color> for BorderColor {
fn from(value: Color) -> Self {
Self::Uniform(value.into())
}
}
#[derive(Clone, Debug)]
pub struct BorderWidths {
pub left: KeyOrValue<f64>,
pub top: KeyOrValue<f64>,
pub right: KeyOrValue<f64>,
pub bottom: KeyOrValue<f64>,
}
impl From<f64> for BorderWidths {
fn from(value: f64) -> Self {
Self {
left: value.into(),
top: value.into(),
right: value.into(),
bottom: value.into(),
}
}
}
impl BorderWidths {
pub fn resolve(&self, env: &Env) -> (f64, f64, f64, f64) {
(
self.left.resolve(env),
self.top.resolve(env),
self.right.resolve(env),
self.bottom.resolve(env),
)
}
}

View file

@ -4,6 +4,7 @@ use std::sync::Arc;
use druid::text::Formatter;
use druid::{Data, Widget};
pub mod border;
pub mod button;
pub mod controller;

View file

@ -18,7 +18,8 @@ use crate::state::{
ACTION_SELECT_MOD, ACTION_SET_WINDOW_HANDLE, ACTION_START_DELETE_SELECTED_MOD,
ACTION_START_DEPLOY, ACTION_START_RESET_DEPLOYMENT,
};
use crate::ui::theme;
use crate::ui::theme::{self, ColorExt};
use crate::ui::widget::border::Border;
use crate::ui::widget::button::Button;
use crate::ui::widget::controller::{
AutoScrollController, DirtyStateController, ImageLensController,
@ -73,7 +74,7 @@ fn build_top_bar() -> impl Widget<State> {
})
.disabled_if(|data, _| data.is_deployment_in_progress || data.is_reset_in_progress);
Flex::row()
let bar = Flex::row()
.must_fill_main_axis(true)
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
.with_child(
@ -89,14 +90,29 @@ fn build_top_bar() -> impl Widget<State> {
.with_child(reset_button),
)
.padding(theme::TOP_BAR_INSETS)
.background(theme::TOP_BAR_BACKGROUND_COLOR)
// TODO: Add bottom border. Need a custom widget for that, as the built-in only provides
// uniform borders on all sides
.background(theme::TOP_BAR_BACKGROUND_COLOR);
Border::new(bar)
.with_color(theme::COLOR_FG2)
.with_bottom_border(1.)
}
fn build_mod_list() -> impl Widget<State> {
let list = List::new(|| {
let checkbox = Checkbox::new("")
.env_scope(|env, selected| {
env.set(druid::theme::BORDER_DARK, theme::COLOR_BG3);
env.set(druid::theme::BORDER_LIGHT, theme::COLOR_BG3);
env.set(druid::theme::TEXT_COLOR, theme::COLOR_ACCENT_FG);
if *selected {
env.set(druid::theme::BACKGROUND_DARK, theme::COLOR_ACCENT);
env.set(druid::theme::BACKGROUND_LIGHT, theme::COLOR_ACCENT);
} else {
env.set(druid::theme::BACKGROUND_DARK, Color::TRANSPARENT);
env.set(druid::theme::BACKGROUND_LIGHT, Color::TRANSPARENT);
}
})
.lens(lens!((usize, Arc<ModInfo>, bool), 1).then(ModInfo::enabled.in_arc()));
let name =
@ -112,13 +128,17 @@ fn build_mod_list() -> impl Widget<State> {
.env_scope(|env, (i, _, selected)| {
if *selected {
env.set(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR, theme::COLOR_ACCENT);
env.set(druid::theme::TEXT_COLOR, theme::COLOR_ACCENT_FG);
env.set(
druid::theme::TEXT_COLOR,
theme::COLOR_ACCENT_FG.darken(0.05),
);
} else {
env.set(druid::theme::TEXT_COLOR, theme::COLOR_FG);
if (i % 2) == 1 {
env.set(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR, theme::COLOR_BG1);
} else {
env.set(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR, Color::TRANSPARENT);
env.set(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR, theme::COLOR_BG);
}
}
})
@ -298,7 +318,7 @@ fn build_mod_details() -> impl Widget<State> {
.cross_axis_alignment(CrossAxisAlignment::Start)
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
.with_flex_child(build_mod_details_info(), 1.0)
.with_child(build_mod_details_buttons().padding(4.))
.with_child(build_mod_details_buttons().padding((4., 4., 4., 8.)))
}
fn build_view_mods() -> impl Widget<State> {
@ -373,7 +393,11 @@ fn build_log_view() -> impl Widget<State> {
.vertical()
.controller(AutoScrollController);
SizedBox::new(label).expand_width().height(128.0)
let inner = Border::new(label)
.with_color(theme::COLOR_FG2)
.with_top_border(1.);
SizedBox::new(inner).expand_width().height(128.0)
}
fn build_window() -> impl Widget<State> {