mirror of
https://github.com/danbulant/cushy
synced 2026-07-05 03:00:43 +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
|
returns a `plotters::DrawingArea` that can be used to draw any plot that the
|
||||||
`plotters` crate supports.
|
`plotters` crate supports.
|
||||||
- `Delimiter` is a new widget that is similar to html's `hr` tag.
|
- `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
|
[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
|
- `Window::new` no longer accepts a `Cushy` parameter. The window now adopts the
|
||||||
`Cushy` from the application it is opened within.
|
`Cushy` from the application it is opened within.
|
||||||
- `MakeWidget::into_window()` no longer takes any parameters.
|
- `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
|
### Changed
|
||||||
|
|
||||||
|
|
|
||||||
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -576,6 +576,7 @@ dependencies = [
|
||||||
"interner",
|
"interner",
|
||||||
"kempt",
|
"kempt",
|
||||||
"kludgine",
|
"kludgine",
|
||||||
|
"nominals",
|
||||||
"palette",
|
"palette",
|
||||||
"plotters",
|
"plotters",
|
||||||
"png",
|
"png",
|
||||||
|
|
@ -1479,6 +1480,12 @@ dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nominals"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c1ae56aa93d00075cffa92cf1a4f525bfb6664fe70c062cf793b642e352414f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ pollster = "0.3.0"
|
||||||
png = "0.17.10"
|
png = "0.17.10"
|
||||||
image = { version = "0.24.7", features = ["png"] }
|
image = { version = "0.24.7", features = ["png"] }
|
||||||
plotters = { version = "0.3.5", default-features = false, optional = true }
|
plotters = { version = "0.3.5", default-features = false, optional = true }
|
||||||
|
nominals = "0.2.1"
|
||||||
|
|
||||||
|
|
||||||
# [patch.crates-io]
|
# [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`.
|
/// Sets the font family to the first family in `list`.
|
||||||
pub fn set_available_font_family(&mut self, list: &FontFamilyList) {
|
pub fn set_available_font_family(&mut self, list: &FontFamilyList) {
|
||||||
if self.font_state.current_font_family.as_ref() != Some(list) {
|
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) {
|
if let Some(family) = self.find_available_font_family(list) {
|
||||||
self.set_font_family(family);
|
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::names::Name;
|
||||||
use crate::utils::Lazy;
|
use crate::utils::Lazy;
|
||||||
use crate::value::{Dynamic, IntoValue, Source, Value};
|
use crate::value::{Dynamic, IntoValue, Source, Value};
|
||||||
|
use crate::widgets::input::CowString;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
|
@ -410,6 +411,8 @@ pub enum Component {
|
||||||
FontWeight(Weight),
|
FontWeight(Weight),
|
||||||
/// The style of a font.
|
/// The style of a font.
|
||||||
FontStyle(Style),
|
FontStyle(Style),
|
||||||
|
/// A string value.
|
||||||
|
String(CowString),
|
||||||
|
|
||||||
/// A custom component type.
|
/// A custom component type.
|
||||||
Custom(CustomComponent),
|
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 {
|
impl From<FamilyOwned> for Component {
|
||||||
fn from(value: FamilyOwned) -> Self {
|
fn from(value: FamilyOwned) -> Self {
|
||||||
Self::FontFamily(value)
|
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 {
|
impl From<FontFamilyList> for Component {
|
||||||
fn from(list: FontFamilyList) -> Self {
|
fn from(list: FontFamilyList) -> Self {
|
||||||
Component::custom(list)
|
Component::custom(list)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ use crate::styles::{Dimension, FocusableWidgets, FontFamilyList, VisualOrder};
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_components {
|
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)*
|
$(#$doc)*
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub struct $component;
|
pub struct $component;
|
||||||
|
|
@ -60,7 +60,7 @@ macro_rules! define_components {
|
||||||
impl ComponentDefinition for $component {
|
impl ComponentDefinition for $component {
|
||||||
type ComponentType = $type;
|
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)*);
|
define_components!($type, |_context| $($expr)*);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1122,7 +1122,7 @@ impl<T> Dynamic<T> {
|
||||||
// Technically this trait bound isn't necessary, but it prevents trying
|
// Technically this trait bound isn't necessary, but it prevents trying
|
||||||
// to call new_radio on unsupported types. The MakeWidget/Widget
|
// to call new_radio on unsupported types. The MakeWidget/Widget
|
||||||
// implementations require these bounds (and more).
|
// implementations require these bounds (and more).
|
||||||
T: Clone + Eq,
|
T: Clone + PartialEq,
|
||||||
{
|
{
|
||||||
Radio::new(widget_value, self.clone(), label)
|
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
|
// Technically this trait bound isn't necessary, but it prevents trying
|
||||||
// to call new_select on unsupported types. The MakeWidget/Widget
|
// to call new_select on unsupported types. The MakeWidget/Widget
|
||||||
// implementations require these bounds (and more).
|
// implementations require these bounds (and more).
|
||||||
T: Clone + Eq,
|
T: Clone + PartialEq,
|
||||||
{
|
{
|
||||||
Select::new(widget_value, self.clone(), label)
|
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
|
/// A cheap-to-clone, copy-on-write [`String`] type that masks its contents in
|
||||||
/// [`Debug`] and [`InputStorage`] implementations.
|
/// [`Debug`] and [`InputStorage`] implementations.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! A read-only text widget.
|
//! A read-only text widget.
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::{Display, Write};
|
||||||
|
|
||||||
use figures::units::{Px, UPx};
|
use figures::units::{Px, UPx};
|
||||||
use figures::{Point, Round, Size};
|
use figures::{Point, Round, Size};
|
||||||
|
|
@ -8,7 +8,7 @@ use kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||||
use kludgine::{CanRenderTo, Color, DrawableExt};
|
use kludgine::{CanRenderTo, Color, DrawableExt};
|
||||||
|
|
||||||
use super::input::CowString;
|
use super::input::CowString;
|
||||||
use crate::context::{GraphicsContext, LayoutContext, Trackable};
|
use crate::context::{GraphicsContext, LayoutContext, Trackable, WidgetContext};
|
||||||
use crate::styles::components::TextColor;
|
use crate::styles::components::TextColor;
|
||||||
use crate::value::{Dynamic, Generation, IntoReadOnly, ReadOnly, Value};
|
use crate::value::{Dynamic, Generation, IntoReadOnly, ReadOnly, Value};
|
||||||
use crate::widget::{Widget, WidgetInstance};
|
use crate::widget::{Widget, WidgetInstance};
|
||||||
|
|
@ -26,7 +26,7 @@ pub struct Label<T> {
|
||||||
|
|
||||||
impl<T> Label<T>
|
impl<T> Label<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug + std::fmt::Display + Send + 'static,
|
T: std::fmt::Debug + DynamicDisplay + Send + 'static,
|
||||||
{
|
{
|
||||||
/// Returns a new label that displays `text`.
|
/// Returns a new label that displays `text`.
|
||||||
pub fn new(text: impl IntoReadOnly<T>) -> Self {
|
pub fn new(text: impl IntoReadOnly<T>) -> Self {
|
||||||
|
|
@ -54,7 +54,7 @@ where
|
||||||
context.apply_current_font_settings();
|
context.apply_current_font_settings();
|
||||||
let measured = self.display.map(|text| {
|
let measured = self.display.map(|text| {
|
||||||
self.displayed.clear();
|
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}");
|
tracing::error!("Error invoking Display: {err}");
|
||||||
}
|
}
|
||||||
context
|
context
|
||||||
|
|
@ -75,7 +75,7 @@ where
|
||||||
|
|
||||||
impl<T> Widget for Label<T>
|
impl<T> Widget for Label<T>
|
||||||
where
|
where
|
||||||
T: std::fmt::Debug + std::fmt::Display + Send + 'static,
|
T: std::fmt::Debug + DynamicDisplay + Send + 'static,
|
||||||
{
|
{
|
||||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
||||||
self.display.invalidate_when_changed(context);
|
self.display.invalidate_when_changed(context);
|
||||||
|
|
@ -132,3 +132,46 @@ impl_make_widget!(
|
||||||
Value<String> => String,
|
Value<String> => String,
|
||||||
ReadOnly<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::fmt::Debug;
|
||||||
use std::sync::Arc;
|
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::grid::GridWidgets;
|
||||||
use super::input::CowString;
|
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::value::{IntoValue, MapEach, Source, Value};
|
||||||
use crate::widget::{MakeWidget, WidgetInstance, WidgetList};
|
use crate::widget::{MakeWidget, WidgetInstance, WidgetList};
|
||||||
|
|
||||||
|
|
@ -16,25 +28,198 @@ pub struct List {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn new(children: impl IntoValue<WidgetList>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
children: children.into_value(),
|
children: children.into_value(),
|
||||||
style: Value::Constant(ListStyle::default()),
|
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.
|
/// The style of a [`List`] widget's item indicators.
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub enum ListStyle {
|
pub enum ListStyle {
|
||||||
|
/// This list should have no indicators.
|
||||||
|
None,
|
||||||
|
|
||||||
/// A solid circle indicator, using the unicode bullet indicator.
|
/// A solid circle indicator, using the unicode bullet indicator.
|
||||||
#[default]
|
#[default]
|
||||||
Disc,
|
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.
|
/// A custom list indicator style.
|
||||||
Custom(Arc<dyn ListIndicator>),
|
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 {
|
impl PartialEq for ListStyle {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
|
|
@ -48,14 +233,153 @@ impl PartialEq for ListStyle {
|
||||||
/// given list index.
|
/// given list index.
|
||||||
pub trait ListIndicator: Debug + Sync + Send + 'static {
|
pub trait ListIndicator: Debug + Sync + Send + 'static {
|
||||||
/// Returns the indicator to use at `index`.
|
/// 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 {
|
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 {
|
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::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()
|
.enumerate()
|
||||||
.map(|(index, child)| {
|
.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(),
|
child.clone().align_left().make_widget(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.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>
|
impl<T> MakeWidgetWithTag for Radio<T>
|
||||||
where
|
where
|
||||||
T: Clone + Debug + Eq + Send + 'static,
|
T: Clone + Debug + PartialEq + Send + 'static,
|
||||||
{
|
{
|
||||||
fn make_with_tag(self, id: crate::widget::WidgetTag) -> WidgetInstance {
|
fn make_with_tag(self, id: crate::widget::WidgetTag) -> WidgetInstance {
|
||||||
RadioOrnament {
|
RadioOrnament {
|
||||||
|
|
@ -78,7 +78,7 @@ struct RadioOrnament<T> {
|
||||||
|
|
||||||
impl<T> Widget for RadioOrnament<T>
|
impl<T> Widget for RadioOrnament<T>
|
||||||
where
|
where
|
||||||
T: Debug + Eq + Send + 'static,
|
T: Debug + PartialEq + Send + 'static,
|
||||||
{
|
{
|
||||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
||||||
let radio_size = context
|
let radio_size = context
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ impl<T> Select<T> {
|
||||||
|
|
||||||
impl<T> MakeWidgetWithTag for Select<T>
|
impl<T> MakeWidgetWithTag for Select<T>
|
||||||
where
|
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 {
|
fn make_with_tag(self, id: crate::widget::WidgetTag) -> WidgetInstance {
|
||||||
let selected = self.state.map_each({
|
let selected = self.state.map_each({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue