Skip to content

Instantly share code, notes, and snippets.

@Philipp-M
Created May 26, 2024 12:19
Show Gist options
  • Save Philipp-M/06cbbcc2efb9151c86760a3954c6b4dd to your computer and use it in GitHub Desktop.
Save Philipp-M/06cbbcc2efb9151c86760a3954c6b4dd to your computer and use it in GitHub Desktop.
strong-typed styling xilem with traits
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