Added List widget

This commit is contained in:
Jonathan Johnson 2024-01-18 13:29:50 -08:00
parent 76a42e2788
commit e2e5085b1f
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
13 changed files with 578 additions and 20 deletions

View file

@ -213,6 +213,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
returns a `plotters::DrawingArea` that can be used to draw any plot that the
`plotters` crate supports.
- `Delimiter` is a new widget that is similar to html's `hr` tag.
- `List` is a new widget that creates lists similar to HTML's `ol` and `ul`
tags.
[plotters]: https://github.com/plotters-rs/plotters
@ -233,6 +235,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Window::new` no longer accepts a `Cushy` parameter. The window now adopts the
`Cushy` from the application it is opened within.
- `MakeWidget::into_window()` no longer takes any parameters.
- `Label<T>` is now generic over a new trait: `DynamicDisplay`. This new trait
allows a way to query a `WidgetContext` to resolve the value to display. The
trait is automatically implemented for all types that implement `Display`, so
this change in practice shouldn't break much code.
### Changed

7
Cargo.lock generated
View file

@ -576,6 +576,7 @@ dependencies = [
"interner",
"kempt",
"kludgine",
"nominals",
"palette",
"plotters",
"png",
@ -1479,6 +1480,12 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nominals"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1ae56aa93d00075cffa92cf1a4f525bfb6664fe70c062cf793b642e352414f"
[[package]]
name = "nu-ansi-term"
version = "0.46.0"

View file

@ -44,6 +44,7 @@ pollster = "0.3.0"
png = "0.17.10"
image = { version = "0.24.7", features = ["png"] }
plotters = { version = "0.3.5", default-features = false, optional = true }
nominals = "0.2.1"
# [patch.crates-io]

28
examples/list.rs Normal file
View file

@ -0,0 +1,28 @@
use cushy::value::Dynamic;
use cushy::widget::{MakeWidget, WidgetList};
use cushy::widgets::list::ListStyle;
use cushy::Run;
fn main() -> cushy::Result {
let current_style: Dynamic<ListStyle> = Dynamic::default();
let options = ListStyle::provided()
.into_iter()
.map(|style| current_style.new_radio(style.clone(), format!("{style:?}")))
.collect::<WidgetList>();
let rows = (1..100).map(|i| i.to_string()).collect::<WidgetList>();
options
.into_rows()
.vertical_scroll()
.and(
rows.into_list()
.style(current_style)
.vertical_scroll()
.expand(),
)
.into_columns()
.expand()
.pad()
.run()
}

View file

@ -81,10 +81,10 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
/// Sets the font family to the first family in `list`.
pub fn set_available_font_family(&mut self, list: &FontFamilyList) {
if self.font_state.current_font_family.as_ref() != Some(list) {
self.font_state.current_font_family = Some(list.clone());
if let Some(family) = self.find_available_font_family(list) {
self.set_font_family(family);
}
self.font_state.current_font_family = Some(list.clone());
}
}

View file

@ -25,6 +25,7 @@ use crate::context::{Trackable, WidgetContext};
use crate::names::Name;
use crate::utils::Lazy;
use crate::value::{Dynamic, IntoValue, Source, Value};
use crate::widgets::input::CowString;
#[macro_use]
pub mod components;
@ -410,6 +411,8 @@ pub enum Component {
FontWeight(Weight),
/// The style of a font.
FontStyle(Style),
/// A string value.
String(CowString),
/// A custom component type.
Custom(CustomComponent),
@ -444,6 +447,38 @@ impl Component {
}
}
macro_rules! impl_component_from_string {
($type:ty) => {
impl From<$type> for Component {
fn from(s: $type) -> Self {
Self::String(s.into())
}
}
};
}
impl_component_from_string!(String);
impl_component_from_string!(CowString);
impl_component_from_string!(&'_ str);
macro_rules! impl_component_try_from_string {
($type:ty) => {
impl TryFrom<Component> for $type {
type Error = Component;
fn try_from(s: Component) -> Result<Self, Self::Error> {
match s {
Component::String(s) => Ok(s.into()),
other => Err(other),
}
}
}
};
}
impl_component_try_from_string!(String);
impl_component_try_from_string!(CowString);
impl From<FamilyOwned> for Component {
fn from(value: FamilyOwned) -> Self {
Self::FontFamily(value)
@ -2548,6 +2583,18 @@ impl From<Vec<FamilyOwned>> for FontFamilyList {
}
}
impl IntoValue<FontFamilyList> for FamilyOwned {
fn into_value(self) -> Value<FontFamilyList> {
FontFamilyList::from(self).into_value()
}
}
impl IntoValue<FontFamilyList> for Vec<FamilyOwned> {
fn into_value(self) -> Value<FontFamilyList> {
FontFamilyList::from(self).into_value()
}
}
impl From<FontFamilyList> for Component {
fn from(list: FontFamilyList) -> Self {
Component::custom(list)

View file

@ -39,7 +39,7 @@ use crate::styles::{Dimension, FocusableWidgets, FontFamilyList, VisualOrder};
/// ```
#[macro_export]
macro_rules! define_components {
($($widget:ident { $($(#$doc:tt)* $component:ident($type:ty, $name:expr, $($default:tt)*))* })*) => {$($(
($($widget:ident { $($(#$doc:tt)* $component:ident($type:ty, $name:expr $(, $($default:tt)*)?))* })*) => {$($(
$(#$doc)*
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct $component;
@ -60,7 +60,7 @@ macro_rules! define_components {
impl ComponentDefinition for $component {
type ComponentType = $type;
define_components!($type, $($default)*);
define_components!($type, $($($default)*)?);
}
};
@ -84,7 +84,10 @@ macro_rules! define_components {
])
});
};
($type:ty, $($expr:tt)*) => {
($type:ty, ) => {
define_components!($type, |_context| <$type>::default());
};
($type:ty, $($expr:tt)+) => {
define_components!($type, |_context| $($expr)*);
};
}

View file

@ -1122,7 +1122,7 @@ impl<T> Dynamic<T> {
// Technically this trait bound isn't necessary, but it prevents trying
// to call new_radio on unsupported types. The MakeWidget/Widget
// implementations require these bounds (and more).
T: Clone + Eq,
T: Clone + PartialEq,
{
Radio::new(widget_value, self.clone(), label)
}
@ -1137,7 +1137,7 @@ impl<T> Dynamic<T> {
// Technically this trait bound isn't necessary, but it prevents trying
// to call new_select on unsupported types. The MakeWidget/Widget
// implementations require these bounds (and more).
T: Clone + Eq,
T: Clone + PartialEq,
{
Select::new(widget_value, self.clone(), label)
}

View file

@ -1385,6 +1385,15 @@ where
}
}
impl From<CowString> for String {
fn from(s: CowString) -> Self {
match Arc::try_unwrap(s.0) {
Ok(s) => s,
Err(arc) => (*arc).clone(),
}
}
}
/// A cheap-to-clone, copy-on-write [`String`] type that masks its contents in
/// [`Debug`] and [`InputStorage`] implementations.
///

View file

@ -1,6 +1,6 @@
//! A read-only text widget.
use std::fmt::Write;
use std::fmt::{Display, Write};
use figures::units::{Px, UPx};
use figures::{Point, Round, Size};
@ -8,7 +8,7 @@ use kludgine::text::{MeasuredText, Text, TextOrigin};
use kludgine::{CanRenderTo, Color, DrawableExt};
use super::input::CowString;
use crate::context::{GraphicsContext, LayoutContext, Trackable};
use crate::context::{GraphicsContext, LayoutContext, Trackable, WidgetContext};
use crate::styles::components::TextColor;
use crate::value::{Dynamic, Generation, IntoReadOnly, ReadOnly, Value};
use crate::widget::{Widget, WidgetInstance};
@ -26,7 +26,7 @@ pub struct Label<T> {
impl<T> Label<T>
where
T: std::fmt::Debug + std::fmt::Display + Send + 'static,
T: std::fmt::Debug + DynamicDisplay + Send + 'static,
{
/// Returns a new label that displays `text`.
pub fn new(text: impl IntoReadOnly<T>) -> Self {
@ -54,7 +54,7 @@ where
context.apply_current_font_settings();
let measured = self.display.map(|text| {
self.displayed.clear();
if let Err(err) = write!(&mut self.displayed, "{text}") {
if let Err(err) = write!(&mut self.displayed, "{}", text.as_display(context)) {
tracing::error!("Error invoking Display: {err}");
}
context
@ -75,7 +75,7 @@ where
impl<T> Widget for Label<T>
where
T: std::fmt::Debug + std::fmt::Display + Send + 'static,
T: std::fmt::Debug + DynamicDisplay + Send + 'static,
{
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
self.display.invalidate_when_changed(context);
@ -132,3 +132,46 @@ impl_make_widget!(
Value<String> => String,
ReadOnly<String> => String
);
/// A context-aware [`Display`] implementation.
///
/// This trait is automatically implemented for all types that implement
/// [`Display`].
pub trait DynamicDisplay {
/// Format `self` with any needed information from `context`.
fn fmt(&self, context: &WidgetContext<'_>, f: &mut std::fmt::Formatter<'_>)
-> std::fmt::Result;
/// Returns a type that implements [`Display`].
fn as_display<'display, 'ctx>(
&'display self,
context: &'display WidgetContext<'ctx>,
) -> DynamicDisplayer<'display, 'ctx>
where
Self: Sized,
{
DynamicDisplayer(self, context)
}
}
impl<T> DynamicDisplay for T
where
T: Display,
{
fn fmt(
&self,
_context: &WidgetContext<'_>,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
self.fmt(f)
}
}
/// A generic [`Display`] implementation for a [`DynamicDisplay`] implementor.
pub struct DynamicDisplayer<'a, 'w>(&'a dyn DynamicDisplay, &'a WidgetContext<'w>);
impl Display for DynamicDisplayer<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(self.1, f)
}
}

View file

@ -3,9 +3,21 @@
use std::fmt::Debug;
use std::sync::Arc;
use nominals::{
ArmenianLower, ArmenianUpper, Bengali, Cambodian, CjkDecimal, CjkEarthlyBranch,
CjkHeavenlyStem, Decimal, Devanagari, DigitCollection, EasternArabic, Ethiopic, Georgian,
GreekLower, GreekUpper, Gujarati, Gurmukhi, HangeulFormal, HangeulInformal, HangeulJamo,
HangeulSyllable, HanjaFormal, Hebrew, HexLower, HexUpper, Hiragana, HiraganaIroha,
JapaneseFormal, JapaneseInformal, Kannada, Katakana, KatakanaIroha, Lao, LetterLower,
LetterUpper, Malayalam, Mongolian, Myanmar, NominalSystem, Oriya, Persian, RomanLower,
RomanUpper, Tamil, Telugu, Thai, Tibetan,
};
use super::grid::GridWidgets;
use super::input::CowString;
use super::Grid;
use super::label::DynamicDisplay;
use super::{Grid, Label};
use crate::styles::{Component, RequireInvalidation};
use crate::value::{IntoValue, MapEach, Source, Value};
use crate::widget::{MakeWidget, WidgetInstance, WidgetList};
@ -16,25 +28,198 @@ pub struct List {
}
impl List {
/// Returns a new list with the default [`ListStyle`]/
/// Returns a new list with the default [`ListStyle`].
#[must_use]
pub fn new(children: impl IntoValue<WidgetList>) -> Self {
Self {
children: children.into_value(),
style: Value::Constant(ListStyle::default()),
}
}
/// Sets the style of list identifiers to `style`.
#[must_use]
pub fn style(mut self, style: impl IntoValue<ListStyle>) -> Self {
self.style = style.into_value();
self
}
}
/// The style of a [`List`] widget's item indicators.
#[derive(Default, Debug, Clone)]
pub enum ListStyle {
/// This list should have no indicators.
None,
/// A solid circle indicator, using the unicode bullet indicator.
#[default]
Disc,
/// A hollow circle.
Circle,
/// A filled square.
Square,
/// Decimal digits (0-9).
Decimal,
/// Eastern Arabic digits.
EasternArabic,
/// Persian digits.
Persian,
/// Lowercase Armenian numbering.
ArmenianLower,
/// Uppercase Armenian numbering.
ArmenianUpper,
/// Bengali numeric digits.
Bengali,
/// Cambodian numeric digits.
Cambodian,
/// CJK Han decimal digits.
CjkDecimal,
/// CJK Earthly Branch symbols.
///
/// This digit collection back to [`CjkDecimal`] after the set is enumerated.
CjkEarthlyBranch,
/// CJK Heavenly Stems symbols.
///
/// This digit collection falls back to [`CjkDecimal`] after the set is
/// enumerated.
CjkHeavenlyStem,
/// Devanagari numeric digits.
Devanagari,
/// Ethiopic numerical system.
Ethiopic,
/// Traditional Georgian numbering.
Georgian,
/// Gujarati numeric digits.
Gujarati,
/// Gurmukhi numeric digits.
Gurmukhi,
/// Korean Hangeul numbering.
HangeulFormal,
/// Informal Korean Hangeul numbering.
HangeulInformal,
/// Formal Korean Hanja numbering.
HanjaFormal,
/// Formal Japanese Kanji numbering.
JapaneseFormal,
/// Informal Japanese Kanji numbering.
JapaneseInformal,
/// Kannada numeric digits.
Kannada,
/// Lao numeric digits.
Lao,
/// Malayalam numeric digits.
Malayalam,
/// Mongolian numeric digits.
Mongolian,
/// Myanmar numeric digits.
Myanmar,
/// Oriya numeric digits.
Oriya,
/// Tamil numeric digits.
Tamil,
/// Telugu numeric digits.
Telugu,
/// Thai numeric digits.
Thai,
/// Tibetan numeric digits.
Tibetan,
/// ASCII lowercase alphabet (a-z).
LetterLower,
/// ASCII uppercase alphabet (A-Z).
LetterUpper,
/// Hexadecimal lowercase digits (0-9a-f)
HexLower,
/// Hexadecimal uppercase digits (0-9A-F)
HexUpper,
/// Greek lowercase alphabet.
GreekUpper,
/// Greek uppercase alphabet.
GreekLower,
/// Japanese Hiragana Aiueo alphabet.
Hiragana,
/// Japanese Hiragana Iroha alphabet.
HiraganaIroha,
/// Japanese Katakana Aiueo alphabet.
Katakana,
/// Japanese Katakana Iroha alphabet.
KatakanaIroha,
/// Korean Hangeul Jamo alphabet.
HangeulJamo,
/// Korean Hangeul Syllable alphabet.
HangeulSyllable,
/// Lowercase Roman numerals (i, ii, iii, iv, ...).
RomanLower,
/// Uppercase Roman numerals (I, II, III, IV, ...).
RomanUpper,
/// Hebrew numerals.
Hebrew,
/// A custom list indicator style.
Custom(Arc<dyn ListIndicator>),
}
impl ListStyle {
/// Returns an iterator containing all built-in list styles.
#[must_use]
pub const fn provided() -> impl IntoIterator<Item = ListStyle> {
[
ListStyle::None,
ListStyle::Disc,
ListStyle::Circle,
ListStyle::Square,
ListStyle::Decimal,
ListStyle::ArmenianLower,
ListStyle::ArmenianUpper,
ListStyle::Bengali,
ListStyle::Cambodian,
ListStyle::CjkDecimal,
ListStyle::CjkEarthlyBranch,
ListStyle::CjkHeavenlyStem,
ListStyle::Devanagari,
ListStyle::EasternArabic,
ListStyle::Ethiopic,
ListStyle::Georgian,
ListStyle::GreekLower,
ListStyle::GreekUpper,
ListStyle::Gujarati,
ListStyle::Gurmukhi,
ListStyle::HangeulFormal,
ListStyle::HangeulInformal,
ListStyle::HangeulJamo,
ListStyle::HangeulSyllable,
ListStyle::HanjaFormal,
ListStyle::Hebrew,
ListStyle::HexLower,
ListStyle::HexUpper,
ListStyle::Hiragana,
ListStyle::HiraganaIroha,
ListStyle::JapaneseFormal,
ListStyle::JapaneseInformal,
ListStyle::Kannada,
ListStyle::Katakana,
ListStyle::KatakanaIroha,
ListStyle::Lao,
ListStyle::LetterLower,
ListStyle::LetterUpper,
ListStyle::Malayalam,
ListStyle::Mongolian,
ListStyle::Myanmar,
ListStyle::Oriya,
ListStyle::Persian,
ListStyle::RomanLower,
ListStyle::RomanUpper,
ListStyle::Tamil,
ListStyle::Telugu,
ListStyle::Thai,
ListStyle::Tibetan,
]
}
}
impl PartialEq for ListStyle {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
@ -48,14 +233,153 @@ impl PartialEq for ListStyle {
/// given list index.
pub trait ListIndicator: Debug + Sync + Send + 'static {
/// Returns the indicator to use at `index`.
fn list_indicator(&self, index: usize) -> Option<CowString>;
fn list_indicator(&self, index: usize) -> Option<Indicator>;
}
impl ListIndicator for ListStyle {
fn list_indicator(&self, index: usize) -> Option<CowString> {
#[allow(clippy::too_many_lines)] // can't avoid the match
fn list_indicator(&self, index: usize) -> Option<Indicator> {
match self {
ListStyle::Disc => Some(CowString::new("\u{2022}")),
ListStyle::None => None,
ListStyle::Decimal => Some(Indicator::delimited(String::from(
Decimal.one_based().format_nominal(index),
))),
ListStyle::Disc => Some(Indicator::bare(CowString::new("\u{2022}"))),
ListStyle::Circle => Some(Indicator::bare(CowString::new("\u{25E6}"))),
ListStyle::Square => Some(Indicator::bare(CowString::new("\u{25AA}"))),
ListStyle::Custom(style) => style.list_indicator(index),
ListStyle::EasternArabic => Some(Indicator::delimited(String::from(
EasternArabic.one_based().format_nominal(index),
))),
ListStyle::Persian => Some(Indicator::delimited(String::from(
Persian.one_based().format_nominal(index),
))),
ListStyle::LetterLower => Some(Indicator::delimited(String::from(
LetterLower.one_based().format_nominal(index),
))),
ListStyle::LetterUpper => Some(Indicator::delimited(String::from(
LetterUpper.one_based().format_nominal(index),
))),
ListStyle::HexLower => Some(Indicator::delimited(String::from(
HexLower.one_based().format_nominal(index),
))),
ListStyle::HexUpper => Some(Indicator::delimited(String::from(
HexUpper.one_based().format_nominal(index),
))),
ListStyle::GreekUpper => Some(Indicator::delimited(String::from(
GreekUpper.one_based().format_nominal(index),
))),
ListStyle::GreekLower => Some(Indicator::delimited(String::from(
GreekLower.one_based().format_nominal(index),
))),
ListStyle::Hiragana => Some(Indicator::delimited(String::from(
Hiragana.one_based().format_nominal(index),
))),
ListStyle::HiraganaIroha => Some(Indicator::delimited(String::from(
HiraganaIroha.one_based().format_nominal(index),
))),
ListStyle::Katakana => Some(Indicator::delimited(String::from(
Katakana.one_based().format_nominal(index),
))),
ListStyle::KatakanaIroha => Some(Indicator::delimited(String::from(
KatakanaIroha.one_based().format_nominal(index),
))),
ListStyle::HangeulJamo => Some(Indicator::delimited(String::from(
HangeulJamo.one_based().format_nominal(index),
))),
ListStyle::HangeulSyllable => Some(Indicator::delimited(String::from(
HangeulSyllable.one_based().format_nominal(index),
))),
ListStyle::RomanLower => Some(Indicator::delimited(String::from(
RomanLower.format_nominal(index),
))),
ListStyle::RomanUpper => Some(Indicator::delimited(String::from(
RomanUpper.format_nominal(index),
))),
ListStyle::Hebrew => Some(Indicator::delimited(String::from(
Hebrew.format_nominal(index),
))),
ListStyle::ArmenianLower => Some(Indicator::delimited(String::from(
ArmenianLower.format_nominal(index),
))),
ListStyle::ArmenianUpper => Some(Indicator::delimited(String::from(
ArmenianUpper.format_nominal(index),
))),
ListStyle::Bengali => Some(Indicator::delimited(String::from(
Bengali.one_based().format_nominal(index),
))),
ListStyle::Cambodian => Some(Indicator::delimited(String::from(
Cambodian.one_based().format_nominal(index),
))),
ListStyle::CjkDecimal => Some(Indicator::delimited(String::from(
CjkDecimal.one_based().format_nominal(index),
))),
ListStyle::CjkEarthlyBranch => Some(Indicator::delimited(String::from(
CjkEarthlyBranch.one_based().format_nominal(index),
))),
ListStyle::CjkHeavenlyStem => Some(Indicator::delimited(String::from(
CjkHeavenlyStem.one_based().format_nominal(index),
))),
ListStyle::Devanagari => Some(Indicator::delimited(String::from(
Devanagari.one_based().format_nominal(index),
))),
ListStyle::Ethiopic => Some(Indicator::delimited(String::from(
Ethiopic.format_nominal(index),
))),
ListStyle::Georgian => Some(Indicator::delimited(String::from(
Georgian.format_nominal(index),
))),
ListStyle::Gujarati => Some(Indicator::delimited(String::from(
Gujarati.one_based().format_nominal(index),
))),
ListStyle::Gurmukhi => Some(Indicator::delimited(String::from(
Gurmukhi.one_based().format_nominal(index),
))),
ListStyle::HangeulFormal => Some(Indicator::delimited(String::from(
HangeulFormal.format_nominal(index),
))),
ListStyle::HangeulInformal => Some(Indicator::delimited(String::from(
HangeulInformal.format_nominal(index),
))),
ListStyle::HanjaFormal => Some(Indicator::delimited(String::from(
HanjaFormal.format_nominal(index),
))),
ListStyle::JapaneseFormal => Some(Indicator::delimited(String::from(
JapaneseFormal.format_nominal(index),
))),
ListStyle::JapaneseInformal => Some(Indicator::delimited(String::from(
JapaneseInformal.format_nominal(index),
))),
ListStyle::Kannada => Some(Indicator::delimited(String::from(
Kannada.one_based().format_nominal(index),
))),
ListStyle::Lao => Some(Indicator::delimited(String::from(
Lao.one_based().format_nominal(index),
))),
ListStyle::Malayalam => Some(Indicator::delimited(String::from(
Malayalam.one_based().format_nominal(index),
))),
ListStyle::Mongolian => Some(Indicator::delimited(String::from(
Mongolian.one_based().format_nominal(index),
))),
ListStyle::Myanmar => Some(Indicator::delimited(String::from(
Myanmar.one_based().format_nominal(index),
))),
ListStyle::Oriya => Some(Indicator::delimited(String::from(
Oriya.one_based().format_nominal(index),
))),
ListStyle::Tamil => Some(Indicator::delimited(String::from(
Tamil.one_based().format_nominal(index),
))),
ListStyle::Telugu => Some(Indicator::delimited(String::from(
Telugu.one_based().format_nominal(index),
))),
ListStyle::Thai => Some(Indicator::delimited(String::from(
Thai.one_based().format_nominal(index),
))),
ListStyle::Tibetan => Some(Indicator::delimited(String::from(
Tibetan.one_based().format_nominal(index),
))),
}
}
}
@ -86,9 +410,99 @@ fn build_grid_widgets(style: &ListStyle, children: &WidgetList) -> GridWidgets<2
.enumerate()
.map(|(index, child)| {
(
style.list_indicator(index).unwrap_or_default(),
Label::new(
style
.list_indicator(index.wrapping_add(1))
.unwrap_or_default(),
)
.align_right(),
child.clone().align_left().make_widget(),
)
})
.collect()
}
/// An indicator used in a [`List`] widget.
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct Indicator {
display: CowString,
delimited: bool,
}
impl Indicator {
/// Returns an indicator that should show a [`Delimiter`] between itself and
/// the list item.
pub fn delimited(display: impl Into<CowString>) -> Self {
Self {
display: display.into(),
delimited: true,
}
}
/// Returns an indicator that skips rendering a [`Delimiter`].
pub fn bare(display: impl Into<CowString>) -> Self {
Self {
display: display.into(),
delimited: false,
}
}
}
impl DynamicDisplay for Indicator {
fn fmt(
&self,
context: &crate::context::WidgetContext<'_>,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let prefix = context.get(&Prefix);
if self.delimited {
let delimiter = context.get(&TrailingDelimiter);
write!(f, "{}{}{}", prefix.0, self.display, delimiter.0)
} else {
write!(f, "{}{}", prefix.0, self.display)
}
}
}
/// A [`CowString`] type used in [`List`] widget style components.
#[derive(Default)]
pub struct ListDelimiter(CowString);
impl From<&'_ str> for ListDelimiter {
fn from(value: &'_ str) -> Self {
Self(value.into())
}
}
impl From<ListDelimiter> for Component {
fn from(value: ListDelimiter) -> Self {
Component::String(value.0)
}
}
impl TryFrom<Component> for ListDelimiter {
type Error = Component;
fn try_from(value: Component) -> Result<Self, Self::Error> {
CowString::try_from(value).map(Self)
}
}
impl RequireInvalidation for ListDelimiter {
fn requires_invalidation(&self) -> bool {
true
}
}
define_components! {
List {
/// The delimiter to place between nested lists when using merged list
/// indicators.
Delimiter(ListDelimiter, "delimiter", ".".into())
/// The delimiter to place after the list indictor, when the list is
/// ordered.
TrailingDelimiter(ListDelimiter, "trailing_delimiter", @Delimiter)
/// The prefix to display before the list indicator.
Prefix(ListDelimiter, "prefix")
}
}

View file

@ -52,7 +52,7 @@ impl<T> Radio<T> {
impl<T> MakeWidgetWithTag for Radio<T>
where
T: Clone + Debug + Eq + Send + 'static,
T: Clone + Debug + PartialEq + Send + 'static,
{
fn make_with_tag(self, id: crate::widget::WidgetTag) -> WidgetInstance {
RadioOrnament {
@ -78,7 +78,7 @@ struct RadioOrnament<T> {
impl<T> Widget for RadioOrnament<T>
where
T: Debug + Eq + Send + 'static,
T: Debug + PartialEq + Send + 'static,
{
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
let radio_size = context

View file

@ -47,7 +47,7 @@ impl<T> Select<T> {
impl<T> MakeWidgetWithTag for Select<T>
where
T: Clone + Debug + Eq + Send + Sync + 'static,
T: Clone + Debug + PartialEq + Send + Sync + 'static,
{
fn make_with_tag(self, id: crate::widget::WidgetTag) -> WidgetInstance {
let selected = self.state.map_each({