Created
May 26, 2024 12:19
-
-
Save Philipp-M/06cbbcc2efb9151c86760a3954c6b4dd to your computer and use it in GitHub Desktop.
strong-typed styling xilem with traits
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod view { | |
use std::marker::PhantomData; | |
pub struct Color; | |
pub trait WithFgColor { | |
fn set_fg_color(&mut self, color: Color); | |
fn fg_color(&self) -> &Color; | |
} | |
pub trait WithBgColor { | |
fn set_bg_color(&mut self, color: Color); | |
fn bg_color(&self) -> &Color; | |
} | |
pub trait WithTextFgColor { | |
fn set_text_fg_color(&mut self, color: Color); | |
fn text_fg_color(&self) -> &Color; | |
} | |
pub trait View<S, A> { | |
type Element; | |
} | |
pub struct FgColor<V, S, A> { | |
view: V, | |
phantom: PhantomData<fn() -> (S, A)>, | |
color: Color, | |
} | |
// This currently requires the feature `associated_type_bounds` but it will be stable in 1.79 | |
impl<S, A, V: View<S, A, Element: WithFgColor>> View<S, A> for FgColor<V, S, A> { | |
type Element = V::Element; | |
} | |
pub struct TextFgColor<V, S, A> { | |
view: V, | |
phantom: PhantomData<fn() -> (S, A)>, | |
color: Color, | |
} | |
// This currently requires the feature `associated_type_bounds` but it will be stable in 1.79 | |
impl<S, A, V: View<S, A, Element: WithTextFgColor>> View<S, A> for TextFgColor<V, S, A> { | |
type Element = V::Element; | |
} | |
pub struct BgIsInvertOfFg<V, S, A> { | |
view: V, | |
phantom: PhantomData<fn() -> (S, A)>, | |
} | |
impl<S, A, V: View<S, A>> View<S, A> for BgIsInvertOfFg<V, S, A> { | |
type Element = V::Element; | |
} | |
pub struct Button<C>(C); | |
pub struct ButtonWidget<C = ()> { | |
fg: Color, | |
bg: Color, | |
content: C, | |
} | |
impl<S, A, C: View<S, A>> View<S, A> for Button<C> { | |
type Element = ButtonWidget<C::Element>; | |
} | |
impl<C> WithFgColor for ButtonWidget<C> { | |
fn set_fg_color(&mut self, color: Color) { | |
self.fg = color; | |
} | |
fn fg_color(&self) -> &Color { | |
&self.fg | |
} | |
} | |
impl<C> WithBgColor for ButtonWidget<C> { | |
fn set_bg_color(&mut self, color: Color) { | |
self.bg = color; | |
} | |
fn bg_color(&self) -> &Color { | |
&self.bg | |
} | |
} | |
impl<C: WithTextFgColor> WithTextFgColor for ButtonWidget<C> { | |
fn set_text_fg_color(&mut self, color: Color) { | |
self.content.set_text_fg_color(color); | |
} | |
fn text_fg_color(&self) -> &Color { | |
self.content.text_fg_color() | |
} | |
} | |
pub struct TextWidget; | |
impl WithTextFgColor for TextWidget { | |
fn set_text_fg_color(&mut self, color: Color) { | |
todo!() | |
} | |
fn text_fg_color(&self) -> &Color { | |
todo!() | |
} | |
} | |
impl<S, A> View<S, A> for &'static str { | |
type Element = TextWidget; | |
} | |
pub fn button(label: &'static str) -> Button<&'static str> { | |
Button(label) | |
} | |
pub trait Style<S, A>: View<S, A> { | |
fn fg(self, color: Color) -> FgColor<Self, S, A> | |
where | |
Self: Sized, | |
Self::Element: WithFgColor, | |
{ | |
FgColor { | |
view: self, | |
color, | |
phantom: PhantomData, | |
} | |
} | |
fn text_fg(self, color: Color) -> TextFgColor<Self, S, A> | |
where | |
Self: Sized, | |
Self::Element: WithTextFgColor, | |
{ | |
TextFgColor { | |
view: self, | |
color, | |
phantom: PhantomData, | |
} | |
} | |
fn bg_is_invert_of_fg(self) -> BgIsInvertOfFg<Self, S, A> | |
where | |
Self: Sized, | |
Self::Element: WithFgColor + WithBgColor, | |
{ | |
BgIsInvertOfFg { | |
view: self, | |
phantom: PhantomData, | |
} | |
} | |
} | |
impl<S, A, V: View<S, A>> Style<S, A> for V {} | |
} | |
use view::{button, ButtonWidget, Color, Style as _, TextWidget, View}; | |
/// user not able to style the button component | |
fn _button_with_color_hiding_element_type() -> impl View<(), ()> { | |
button("hello").fg(Color) | |
} | |
/// user able to style this button component | |
fn button_with_color() -> impl View<(), (), Element = ButtonWidget<TextWidget>> { | |
button("hello").fg(Color) | |
} | |
fn main() { | |
let _button_overwriting_previous_colors = button_with_color() | |
.fg(Color) | |
// Note how `text_fg` is "redirected" to the label, and it would not be possible, if the content of the button widget doesn't implement `WithTextFgColor` | |
.text_fg(Color) | |
.fg(Color) | |
.bg_is_invert_of_fg(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment