Make it pretty. #69
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) {
|
pub(crate) fn set_theme_env(env: &mut Env, _: &State) {
|
||||||
env.set(druid::theme::TEXT_COLOR, COLOR_FG);
|
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(druid::theme::BUTTON_BORDER_RADIUS, 2.);
|
||||||
|
|
||||||
env.set(keys::KEY_BUTTON_BG, COLOR_ACCENT);
|
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_HOT, COLOR_ACCENT.darken(0.03));
|
||||||
env.set(keys::KEY_BUTTON_BG_ACTIVE, COLOR_ACCENT.darken(0.1));
|
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::text::Formatter;
|
||||||
use druid::{Data, Widget};
|
use druid::{Data, Widget};
|
||||||
|
|
||||||
|
pub mod border;
|
||||||
pub mod button;
|
pub mod button;
|
||||||
pub mod controller;
|
pub mod controller;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,8 @@ use crate::state::{
|
||||||
ACTION_SELECT_MOD, ACTION_SET_WINDOW_HANDLE, ACTION_START_DELETE_SELECTED_MOD,
|
ACTION_SELECT_MOD, ACTION_SET_WINDOW_HANDLE, ACTION_START_DELETE_SELECTED_MOD,
|
||||||
ACTION_START_DEPLOY, ACTION_START_RESET_DEPLOYMENT,
|
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::button::Button;
|
||||||
use crate::ui::widget::controller::{
|
use crate::ui::widget::controller::{
|
||||||
AutoScrollController, DirtyStateController, ImageLensController,
|
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);
|
.disabled_if(|data, _| data.is_deployment_in_progress || data.is_reset_in_progress);
|
||||||
|
|
||||||
Flex::row()
|
let bar = Flex::row()
|
||||||
.must_fill_main_axis(true)
|
.must_fill_main_axis(true)
|
||||||
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||||
.with_child(
|
.with_child(
|
||||||
|
@ -89,14 +90,29 @@ fn build_top_bar() -> impl Widget<State> {
|
||||||
.with_child(reset_button),
|
.with_child(reset_button),
|
||||||
)
|
)
|
||||||
.padding(theme::TOP_BAR_INSETS)
|
.padding(theme::TOP_BAR_INSETS)
|
||||||
.background(theme::TOP_BAR_BACKGROUND_COLOR)
|
.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
|
Border::new(bar)
|
||||||
|
.with_color(theme::COLOR_FG2)
|
||||||
|
.with_bottom_border(1.)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_mod_list() -> impl Widget<State> {
|
fn build_mod_list() -> impl Widget<State> {
|
||||||
let list = List::new(|| {
|
let list = List::new(|| {
|
||||||
let checkbox = Checkbox::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()));
|
.lens(lens!((usize, Arc<ModInfo>, bool), 1).then(ModInfo::enabled.in_arc()));
|
||||||
|
|
||||||
let name =
|
let name =
|
||||||
|
@ -112,13 +128,17 @@ fn build_mod_list() -> impl Widget<State> {
|
||||||
.env_scope(|env, (i, _, selected)| {
|
.env_scope(|env, (i, _, selected)| {
|
||||||
if *selected {
|
if *selected {
|
||||||
env.set(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR, theme::COLOR_ACCENT);
|
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 {
|
} else {
|
||||||
env.set(druid::theme::TEXT_COLOR, theme::COLOR_FG);
|
env.set(druid::theme::TEXT_COLOR, theme::COLOR_FG);
|
||||||
|
|
||||||
if (i % 2) == 1 {
|
if (i % 2) == 1 {
|
||||||
env.set(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR, theme::COLOR_BG1);
|
env.set(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR, theme::COLOR_BG1);
|
||||||
} else {
|
} 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)
|
.cross_axis_alignment(CrossAxisAlignment::Start)
|
||||||
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||||
.with_flex_child(build_mod_details_info(), 1.0)
|
.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> {
|
fn build_view_mods() -> impl Widget<State> {
|
||||||
|
@ -373,7 +393,11 @@ fn build_log_view() -> impl Widget<State> {
|
||||||
.vertical()
|
.vertical()
|
||||||
.controller(AutoScrollController);
|
.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> {
|
fn build_window() -> impl Widget<State> {
|
||||||
|
|
Loading…
Add table
Reference in a new issue