From 6229840c3fed2b24f959fd2e28f3931c8e2411aa Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 25 Feb 2023 14:59:19 +0000 Subject: [PATCH] Add kas::theme::SelectionStyle --- crates/kas-core/src/theme/draw.rs | 22 ++++++------- crates/kas-core/src/theme/simple_theme.rs | 24 ++++++++++---- crates/kas-core/src/theme/style.rs | 22 +++++++++++++ crates/kas-macros/src/extends.rs | 6 ++-- crates/kas-view/src/list_view.rs | 39 +++++++++++++++++++++-- crates/kas-view/src/matrix_view.rs | 39 +++++++++++++++++++++-- 6 files changed, 124 insertions(+), 28 deletions(-) diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 280ccb7b1..f42010c04 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -5,7 +5,7 @@ //! "Handle" types used by themes -use super::{FrameStyle, MarkStyle, SizeMgr, TextClass, ThemeSize}; +use super::{FrameStyle, MarkStyle, SelectionStyle, SizeMgr, TextClass, ThemeSize}; use crate::dir::Direction; use crate::draw::color::Rgb; use crate::draw::{Draw, DrawIface, DrawShared, DrawSharedImpl, ImageId, PassType}; @@ -194,13 +194,13 @@ impl<'a> DrawMgr<'a> { self.h.separator(rect); } - /// Draw a selection box + /// Draw a selection highlight / frame /// - /// This appears as a dashed box or similar around this `rect`. Note that - /// the selection indicator is drawn *outside* of this rect, within a margin - /// of size [`SizeMgr::inner_margins`] that is expected to be present around this box. - pub fn selection_box(&mut self, rect: Rect) { - self.h.selection_box(rect); + /// Adjusts the background color and/or draws a line around the given rect. + /// In the latter case, a margin of size [`SizeMgr::inner_margins`] around + /// `rect` is expected. + pub fn selection(&mut self, rect: Rect, style: SelectionStyle) { + self.h.selection(rect, style); } /// Draw text @@ -412,12 +412,8 @@ pub trait ThemeDraw { /// Draw a separator in the given `rect` fn separator(&mut self, rect: Rect); - /// Draw a selection box - /// - /// This appears as a dashed box or similar around this `rect`. Note that - /// the selection indicator is drawn *outside* of this rect, within a margin - /// of size `inner_margin` that is expected to be present around this box. - fn selection_box(&mut self, rect: Rect); + /// Draw a selection highlight / frame + fn selection(&mut self, rect: Rect, style: SelectionStyle); /// Draw text /// diff --git a/crates/kas-core/src/theme/simple_theme.rs b/crates/kas-core/src/theme/simple_theme.rs index 50f419dac..10763916d 100644 --- a/crates/kas-core/src/theme/simple_theme.rs +++ b/crates/kas-core/src/theme/simple_theme.rs @@ -20,7 +20,7 @@ use kas::text::{fonts, Effect, TextApi, TextDisplay}; use kas::theme::dimensions as dim; use kas::theme::{Background, FrameStyle, MarkStyle, TextClass}; use kas::theme::{ColorsLinear, Config, InputState, Theme}; -use kas::theme::{ThemeControl, ThemeDraw, ThemeSize}; +use kas::theme::{SelectionStyle, ThemeControl, ThemeDraw, ThemeSize}; use kas::{Action, WidgetId}; /// A simple theme @@ -362,12 +362,24 @@ where self.draw.rect(outer, self.cols.frame); } - fn selection_box(&mut self, rect: Rect) { + fn selection(&mut self, rect: Rect, style: SelectionStyle) { let inner = Quad::conv(rect); - let outer = inner.grow(self.w.dims.m_inner.into()); - // TODO: this should use its own colour and a stippled pattern - let col = self.cols.text_sel_bg; - self.draw.frame(outer, inner, col); + match style { + SelectionStyle::Highlight => { + self.draw.rect(inner, self.cols.text_sel_bg); + } + SelectionStyle::Frame => { + let outer = inner.grow(self.w.dims.m_inner.into()); + // TODO: this should use its own colour and a stippled pattern + let col = self.cols.accent; + self.draw.frame(outer, inner, col); + } + SelectionStyle::Both => { + let outer = inner.grow(self.w.dims.m_inner.into()); + self.draw.rect(outer, self.cols.accent); + self.draw.rect(inner, self.cols.text_sel_bg); + } + } } fn text(&mut self, id: &WidgetId, rect: Rect, text: &TextDisplay, _: TextClass) { diff --git a/crates/kas-core/src/theme/style.rs b/crates/kas-core/src/theme/style.rs index 57cf73a8d..a5d1b90cb 100644 --- a/crates/kas-core/src/theme/style.rs +++ b/crates/kas-core/src/theme/style.rs @@ -94,6 +94,28 @@ pub enum FrameStyle { Window, } +/// Selection style hint +/// +/// How to draw selections +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub enum SelectionStyle { + /// Adjust background color + Highlight, + /// Draw a frame around the selection + Frame, + /// Both + Both, +} + +impl SelectionStyle { + /// True if an external margin is required + /// + /// Margin size: [`SizeMgr::inner_margins`] + pub fn is_external(self) -> bool { + matches!(self, SelectionStyle::Frame | SelectionStyle::Both) + } +} + /// Class of text drawn /// /// Themes choose font, font size, colour, and alignment based on this. diff --git a/crates/kas-macros/src/extends.rs b/crates/kas-macros/src/extends.rs index f0ad463d9..fb0361fce 100644 --- a/crates/kas-macros/src/extends.rs +++ b/crates/kas-macros/src/extends.rs @@ -64,7 +64,7 @@ impl Extends { (#base).get_clip_rect() } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { + fn frame(&mut self, id: &WidgetId, rect: Rect, style: ::kas::theme::FrameStyle, bg: Background) { (#base).frame(id, rect, style, bg); } @@ -72,8 +72,8 @@ impl Extends { (#base).separator(rect); } - fn selection_box(&mut self, rect: Rect) { - (#base).selection_box(rect); + fn selection(&mut self, rect: Rect, style: ::kas::theme::SelectionStyle) { + (#base).selection(rect, style); } fn text(&mut self, id: &WidgetId, rect: Rect, text: &TextDisplay, class: TextClass) { diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 379d84fca..1fb2d59fb 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -12,6 +12,7 @@ use kas::layout::{solve_size_rules, AlignHints}; #[allow(unused)] use kas::model::SharedData; use kas::model::{ListData, SharedDataMut}; use kas::prelude::*; +use kas::theme::SelectionStyle; #[allow(unused)] // doc links use kas_widgets::ScrollBars; use kas_widgets::SelectMsg; @@ -72,6 +73,7 @@ impl_scope! { child_size: Size, scroll: ScrollComponent, sel_mode: SelectionMode, + sel_style: SelectionStyle, // TODO(opt): replace selection list with RangeOrSet type? selection: LinearSet, press_target: Option<(usize, T::Key)>, @@ -139,6 +141,7 @@ impl_scope! { child_size: Size::ZERO, scroll: Default::default(), sel_mode: SelectionMode::None, + sel_style: SelectionStyle::Highlight, selection: Default::default(), press_target: None, } @@ -236,6 +239,32 @@ impl_scope! { self } + /// Get the current selection style + pub fn selection_style(&self) -> SelectionStyle { + self.sel_style + } + /// Set the current selection style + /// + /// By default, [`SelectionStyle::Highlight`] is used. Other modes may + /// add margin between elements. + pub fn set_selection_style(&mut self, style: SelectionStyle) -> Action { + let action = if style.is_external() != self.sel_style.is_external() { + Action::RESIZE + } else { + Action::empty() + }; + self.sel_style = style; + action + } + /// Set the selection style (inline) + /// + /// See [`Self::set_selection_style`] documentation. + #[must_use] + pub fn with_selection_style(mut self, style: SelectionStyle) -> Self { + self.sel_style = style; + self + } + /// Read the list of selected entries /// /// With mode [`SelectionMode::Single`] this may contain zero or one entry; @@ -473,7 +502,11 @@ impl_scope! { impl Layout for Self { fn size_rules(&mut self, size_mgr: SizeMgr, mut axis: AxisInfo) -> SizeRules { // We use an invisible frame for highlighting selections, drawing into the margin - let inner_margin = size_mgr.inner_margins().extract(axis); + let inner_margin = if self.sel_style.is_external() { + size_mgr.inner_margins().extract(axis) + } else { + (0, 0) + }; let frame = kas::layout::FrameRules::new(0, inner_margin, (0, 0)); let other = axis.other().map(|mut size| { @@ -579,12 +612,12 @@ impl_scope! { let offset = self.scroll_offset(); draw.with_clip_region(self.core.rect, offset, |mut draw| { for child in &mut self.widgets[..self.cur_len.cast()] { - draw.recurse(&mut child.widget); if let Some(ref key) = child.key { if self.selection.contains(key) { - draw.selection_box(child.widget.rect()); + draw.selection(child.widget.rect(), self.sel_style); } } + draw.recurse(&mut child.widget); } }); } diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index f10190ef9..8b21a6087 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -12,6 +12,7 @@ use kas::layout::{solve_size_rules, AlignHints}; #[allow(unused)] use kas::model::SharedData; use kas::model::{MatrixData, SharedDataMut}; use kas::prelude::*; +use kas::theme::SelectionStyle; #[allow(unused)] // doc links use kas_widgets::ScrollBars; use kas_widgets::SelectMsg; @@ -78,6 +79,7 @@ impl_scope! { child_size: Size, scroll: ScrollComponent, sel_mode: SelectionMode, + sel_style: SelectionStyle, // TODO(opt): replace selection list with RangeOrSet type? selection: LinearSet, press_target: Option<(usize, T::Key)>, @@ -115,6 +117,7 @@ impl_scope! { child_size: Size::ZERO, scroll: Default::default(), sel_mode: SelectionMode::None, + sel_style: SelectionStyle::Highlight, selection: Default::default(), press_target: None, } @@ -212,6 +215,32 @@ impl_scope! { self } + /// Get the current selection style + pub fn selection_style(&self) -> SelectionStyle { + self.sel_style + } + /// Set the current selection style + /// + /// By default, [`SelectionStyle::Highlight`] is used. Other modes may + /// add margin between elements. + pub fn set_selection_style(&mut self, style: SelectionStyle) -> Action { + let action = if style.is_external() != self.sel_style.is_external() { + Action::RESIZE + } else { + Action::empty() + }; + self.sel_style = style; + action + } + /// Set the selection style (inline) + /// + /// See [`Self::set_selection_style`] documentation. + #[must_use] + pub fn with_selection_style(mut self, style: SelectionStyle) -> Self { + self.sel_style = style; + self + } + /// Read the list of selected entries /// /// With mode [`SelectionMode::Single`] this may contain zero or one entry; @@ -451,7 +480,11 @@ impl_scope! { impl Layout for Self { fn size_rules(&mut self, size_mgr: SizeMgr, mut axis: AxisInfo) -> SizeRules { // We use an invisible frame for highlighting selections, drawing into the margin - let inner_margin = size_mgr.inner_margins().extract(axis); + let inner_margin = if self.sel_style.is_external() { + size_mgr.inner_margins().extract(axis) + } else { + (0, 0) + }; let frame = kas::layout::FrameRules::new(0, inner_margin, (0, 0)); let other = axis.other().map(|mut size| { @@ -569,10 +602,10 @@ impl_scope! { // visible, so check intersection before drawing: if rect.intersection(&child.widget.rect()).is_some() { if let Some(ref key) = child.key { - draw.recurse(&mut child.widget); if self.selection.contains(key) { - draw.selection_box(child.widget.rect()); + draw.selection(child.widget.rect(), self.sel_style); } + draw.recurse(&mut child.widget); } } }