diff --git a/Cargo.lock b/Cargo.lock index 0ab730a..03f77fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -775,6 +775,7 @@ dependencies = [ "tracing", "tracing-error", "tracing-subscriber", + "usvg", "zip", ] diff --git a/crates/dtmm/Cargo.toml b/crates/dtmm/Cargo.toml index 11b71f7..c98a1e9 100644 --- a/crates/dtmm/Cargo.toml +++ b/crates/dtmm/Cargo.toml @@ -29,3 +29,4 @@ time = { version = "0.3.20", features = ["serde", "serde-well-known", "local-off strip-ansi-escapes = "0.1.1" lazy_static = "1.4.0" colors-transform = "0.2.11" +usvg = "0.25.0" diff --git a/crates/dtmm/src/ui/theme/icons.rs b/crates/dtmm/src/ui/theme/icons.rs index a947871..69156ad 100644 --- a/crates/dtmm/src/ui/theme/icons.rs +++ b/crates/dtmm/src/ui/theme/icons.rs @@ -1 +1,41 @@ +use druid::Color; +use usvg::{ + Error, Fill, LineCap, LineJoin, NodeKind, NonZeroPositiveF64, Options, Paint, Stroke, Tree, +}; + pub static ALERT_CIRCLE: &str = include_str!("../../../assets/icons/icons/alert-circle.svg"); +pub static ALERT_TRIANGLE: &str = include_str!("../../../assets/icons/icons/alert-triangle.svg"); + +pub fn parse_svg(svg: &str) -> Result { + let opt = Options::default(); + Tree::from_str(svg, &opt.to_ref()) +} + +pub fn recolor_icon(tree: Tree, stroke: bool, color: Color) -> Tree { + let (red, green, blue, _) = color.as_rgba8(); + + let mut children = tree.root.children(); + // The first element is always some kind of background placeholder + children.next(); + + for node in children { + if let NodeKind::Path(ref mut path) = *node.borrow_mut() { + if stroke { + path.stroke = Some(Stroke { + paint: Paint::Color(usvg::Color { red, green, blue }), + width: NonZeroPositiveF64::new(2.).expect("the value is not zero"), + linecap: LineCap::Round, + linejoin: LineJoin::Round, + ..Default::default() + }); + } else { + path.fill = Some(Fill { + paint: Paint::Color(usvg::Color { red, green, blue }), + ..Default::default() + }); + } + } + } + + tree +} diff --git a/crates/dtmm/src/ui/window/main.rs b/crates/dtmm/src/ui/window/main.rs index 16be5f0..b251767 100644 --- a/crates/dtmm/src/ui/window/main.rs +++ b/crates/dtmm/src/ui/window/main.rs @@ -2,15 +2,16 @@ use std::str::FromStr; use std::sync::Arc; use druid::im::Vector; +use druid::lens; use druid::widget::{ Checkbox, CrossAxisAlignment, Either, Flex, Image, Label, LineBreaking, List, MainAxisAlignment, Maybe, Scroll, SizedBox, Split, Svg, SvgData, TextBox, ViewSwitcher, }; -use druid::{lens, Data, ImageBuf, LifeCycleCtx}; use druid::{ Color, FileDialogOptions, FileSpec, FontDescriptor, FontFamily, LensExt, SingleUse, Widget, WidgetExt, WindowDesc, WindowId, }; +use druid::{Data, ImageBuf, LifeCycleCtx}; use lazy_static::lazy_static; use crate::state::{ @@ -18,7 +19,7 @@ use crate::state::{ ACTION_SELECT_MOD, ACTION_SET_WINDOW_HANDLE, ACTION_START_CHECK_UPDATE, ACTION_START_DELETE_SELECTED_MOD, ACTION_START_DEPLOY, ACTION_START_RESET_DEPLOYMENT, }; -use crate::ui::theme::{self, ColorExt}; +use crate::ui::theme::{self, ColorExt, COLOR_YELLOW_LIGHT}; use crate::ui::widget::border::Border; use crate::ui::widget::button::Button; use crate::ui::widget::controller::{ @@ -126,19 +127,31 @@ fn build_mod_list() -> impl Widget { let name = Label::raw().lens(lens!((usize, Arc, bool), 1).then(ModInfo::name.in_arc())); - let version = Label::dynamic(|info: &Arc, _| { - let has_update = info - .nexus - .as_ref() - .map(|n| info.version != n.version) - .unwrap_or(false); - if has_update { - format!("! {}", info.version) - } else { - info.version.to_string() - } - }) - .lens(lens!((usize, Arc, bool), 1)); + let version = { + let icon = { + let tree = + theme::icons::parse_svg(theme::icons::ALERT_TRIANGLE).expect("invalid SVG"); + + let tree = theme::icons::recolor_icon(tree, true, COLOR_YELLOW_LIGHT); + + Svg::new(Arc::new(tree)).fix_height(druid::theme::TEXT_SIZE_NORMAL) + }; + + Either::new( + |info, _| { + info.nexus + .as_ref() + .map(|n| info.version != n.version) + .unwrap_or(false) + }, + Flex::row() + .with_child(icon) + .with_spacer(3.) + .with_child(Label::raw().lens(ModInfo::version.in_arc())), + Label::raw().lens(ModInfo::version.in_arc()), + ) + .lens(lens!((usize, Arc, bool), 1)) + }; let fields = Flex::row() .must_fill_main_axis(true)