mirror of
https://github.com/danbulant/cushy
synced 2026-06-17 21:41:11 +00:00
Added List widget
This commit is contained in:
parent
76a42e2788
commit
e2e5085b1f
13 changed files with 578 additions and 20 deletions
|
|
@ -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
7
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
28
examples/list.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)*);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in a new issue