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:
parent
4c33741b03
commit
c38909db22
4 changed files with 234 additions and 9 deletions
|
@ -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));
|
||||
|
|
197
crates/dtmm/src/ui/widget/border.rs
Normal file
197
crates/dtmm/src/ui/widget/border.rs
Normal 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),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Add table
Reference in a new issue