Skip to content

Commit

Permalink
Give all buttons type state builders
Browse files Browse the repository at this point in the history
  • Loading branch information
vE5li committed Mar 4, 2024
1 parent 26e2492 commit ca66635
Show file tree
Hide file tree
Showing 13 changed files with 191 additions and 85 deletions.
17 changes: 17 additions & 0 deletions src/interface/elements/buttons/close/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use super::CloseButton;

/// Type state [`CloseButton`] builder. This builder utilizes the type system to
/// prevent calling the same method multiple times and calling
/// [`build`](Self::build) before the mandatory methods have been called.
pub struct CloseButtonBuilder;

impl CloseButtonBuilder {
pub fn new() -> Self {
Self
}

/// Take the builder and turn it into a [`CloseButton`].
pub fn build(self) -> CloseButton {
CloseButton { state: Default::default() }
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod builder;

pub use self::builder::CloseButtonBuilder;
use crate::graphics::{InterfaceRenderer, Renderer};
use crate::input::MouseInputMode;
use crate::interface::*;

#[derive(Default)]
pub struct CloseButton {
state: ElementState,
}
Expand Down
5 changes: 5 additions & 0 deletions src/interface/elements/buttons/default/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ where
TEXT: AsRef<str> + 'static,
EVENT: ElementEvent + 'static,
{
/// Take the builder and turn it into a [`Button`].
///
/// NOTE: This method is only available if [`with_text`](Self::with_text)
/// and [`with_event`](Self::with_event) have been called on
/// the builder.
pub fn build(self) -> Button<TEXT, EVENT> {
let Self {
text,
Expand Down
1 change: 1 addition & 0 deletions src/interface/elements/buttons/drag/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl<TITLE> DragButtonBuilder<TITLE, Unset> {

impl DragButtonBuilder<With<String>, With<DimensionBound>> {
/// Take the builder and turn it into a [`DragButton`].
///
/// NOTE: This method is only available if [`with_title`](Self::with_title)
/// and [`with_width_bound`](Self::with_width_bound) have been called on
/// the builder.
Expand Down
4 changes: 2 additions & 2 deletions src/interface/elements/buttons/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ mod default;
mod drag;
mod state;

pub use self::close::CloseButton;
pub use self::close::CloseButtonBuilder;
pub use self::default::ButtonBuilder;
pub use self::drag::DragButtonBuilder;
pub use self::state::StateButton;
pub use self::state::StateButtonBuilder;
119 changes: 119 additions & 0 deletions src/interface/elements/buttons/state/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use super::{StateButton, StateSelector};
use crate::interface::builder::{Set, Unset, With};
use crate::interface::*;

/// Type state [`StateButton`] builder. This builder utilizes the type system to
/// prevent calling the same method multiple times and calling
/// [`build`](Self::build) before the mandatory methods have been called.
#[must_use = "`build` needs to be called"]
pub struct StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, WIDTH> {
text: TEXT,
event: EVENT,
selector: SELECTOR,
transparent_background: bool,
width_bound: Option<DimensionBound>,
marker: PhantomData<(SELECTOR, BACKGROUND, WIDTH)>,
}

impl StateButtonBuilder<Unset, Unset, Unset, Unset, Unset> {
pub fn new() -> Self {
Self {
text: Unset,
event: Unset,
selector: Unset,
transparent_background: false,
width_bound: None,
marker: PhantomData,
}
}
}

impl<EVENT, SELECTOR, BACKGROUND, WIDTH> StateButtonBuilder<Unset, EVENT, SELECTOR, BACKGROUND, WIDTH> {
pub fn with_text<TEXT: AsRef<str> + 'static>(self, text: TEXT) -> StateButtonBuilder<With<TEXT>, EVENT, SELECTOR, BACKGROUND, WIDTH> {
StateButtonBuilder {
text: With::new(text),
..self
}
}
}

impl<TEXT, SELECTOR, BACKGROUND, WIDTH> StateButtonBuilder<TEXT, Unset, SELECTOR, BACKGROUND, WIDTH> {
pub fn with_event<EVENT: ElementEvent + 'static>(
self,
event: EVENT,
) -> StateButtonBuilder<TEXT, With<EVENT>, SELECTOR, BACKGROUND, WIDTH> {
StateButtonBuilder {
event: With::new(event),
..self
}
}
}

impl<TEXT, EVENT, BACKGROUND, WIDTH> StateButtonBuilder<TEXT, EVENT, Unset, BACKGROUND, WIDTH> {
pub fn with_selector(
self,
selector: impl Fn(&StateProvider) -> bool + 'static,
) -> StateButtonBuilder<TEXT, EVENT, With<StateSelector>, BACKGROUND, WIDTH> {
StateButtonBuilder {
selector: With::new(Box::new(selector)),
marker: PhantomData,
..self
}
}
}

impl<TEXT, EVENT, SELECTOR, WIDTH> StateButtonBuilder<TEXT, EVENT, SELECTOR, Unset, WIDTH> {
pub fn with_transparent_background(self) -> StateButtonBuilder<TEXT, EVENT, SELECTOR, Set, WIDTH> {
StateButtonBuilder {
transparent_background: true,
marker: PhantomData,
..self
}
}
}

impl<TEXT, EVENT, SELECTOR, BACKGROUND> StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, Unset> {
pub fn with_width_bound(self, width_bound: DimensionBound) -> StateButtonBuilder<TEXT, EVENT, SELECTOR, BACKGROUND, Set> {
StateButtonBuilder {
width_bound: Some(width_bound),
marker: PhantomData,
..self
}
}
}

impl<TEXT, EVENT, BACKGROUND, WIDTH> StateButtonBuilder<With<TEXT>, With<EVENT>, With<StateSelector>, BACKGROUND, WIDTH>
where
TEXT: AsRef<str> + 'static,
EVENT: ElementEvent + 'static,
{
/// Take the builder and turn it into a [`StateButton`].
///
/// NOTE: This method is only available if [`with_text`](Self::with_text),
/// [`with_event`](Self::with_event), and
/// [`with_selector`](Self::with_selector) have been called on
/// the builder.
pub fn build(self) -> StateButton<TEXT, EVENT> {
let Self {
text,
event,
selector,
transparent_background,
width_bound,
..
} = self;

let text = text.take();
let event = event.take();
let selector = selector.take();

StateButton {
text,
event,
selector,
transparent_background,
width_bound,
state: Default::default(),
}
}
}
Original file line number Diff line number Diff line change
@@ -1,77 +1,32 @@
mod builder;

use procedural::dimension_bound;

pub use self::builder::StateButtonBuilder;
use crate::graphics::{InterfaceRenderer, Renderer};
use crate::input::MouseInputMode;
use crate::interface::{Element, *};

type StateSelector = Box<dyn Fn(&StateProvider) -> bool + 'static>;

// FIX: State button won't redraw just because the state changes
pub struct StateButton<T, E>
pub struct StateButton<TEXT, EVENT>
where
T: AsRef<str> + 'static,
E: ElementEvent + 'static,
TEXT: AsRef<str> + 'static,
EVENT: ElementEvent + 'static,
{
text: Option<T>,
selector: Option<Box<dyn Fn(&StateProvider) -> bool>>,
event: Option<E>,
text: TEXT,
event: EVENT,
selector: StateSelector,
width_bound: Option<DimensionBound>,
transparent_background: bool,
state: ElementState,
}

// HACK: Workaround for Rust incorrect trait bounds when deriving Option<T>
// where T: !Default.
impl<T, E> Default for StateButton<T, E>
where
T: AsRef<str> + 'static,
E: ElementEvent + 'static,
{
fn default() -> Self {
Self {
text: Default::default(),
selector: Default::default(),
event: Default::default(),
width_bound: Default::default(),
transparent_background: Default::default(),
state: Default::default(),
}
}
}

impl<T, E> StateButton<T, E>
where
T: AsRef<str> + 'static,
E: ElementEvent + 'static,
{
pub fn with_text(mut self, text: T) -> Self {
self.text = Some(text);
self
}

pub fn with_selector(mut self, selector: impl Fn(&StateProvider) -> bool + 'static) -> Self {
self.selector = Some(Box::new(selector));
self
}

pub fn with_event(mut self, event: E) -> Self {
self.event = Some(event);
self
}

pub fn with_transparent_background(mut self) -> Self {
self.transparent_background = true;
self
}

pub fn with_width(mut self, width_bound: DimensionBound) -> Self {
self.width_bound = Some(width_bound);
self
}
}

impl<T, E> Element for StateButton<T, E>
impl<TEXT, EVENT> Element for StateButton<TEXT, EVENT>
where
T: AsRef<str> + 'static,
E: ElementEvent + 'static,
TEXT: AsRef<str> + 'static,
EVENT: ElementEvent + 'static,
{
fn get_state(&self) -> &ElementState {
&self.state
Expand Down Expand Up @@ -99,7 +54,7 @@ where
}

fn left_click(&mut self, _force_update: &mut bool) -> Vec<ClickAction> {
self.event.as_mut().map(|event| event.trigger()).unwrap_or_default()
self.event.trigger()
}

fn render(
Expand Down Expand Up @@ -140,16 +95,14 @@ where
theme.button.icon_offset.get(),
theme.button.icon_size.get(),
foreground_color,
(self.selector.as_ref().unwrap())(state_provider),
(self.selector)(state_provider),
);

if let Some(text) = &self.text {
renderer.render_text(
text.as_ref(),
theme.button.icon_text_offset.get(),
foreground_color,
theme.button.font_size.get(),
);
}
renderer.render_text(
self.text.as_ref(),
theme.button.icon_text_offset.get(),
foreground_color,
theme.button.font_size.get(),
);
}
}
6 changes: 4 additions & 2 deletions src/interface/windows/account/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,17 +214,19 @@ impl<'a> PrototypeWindow for LoginWindow<'a> {
InputField::<24, true>::new(password, "Password", password_action, dimension_bound!(100%)).wrap(),
Container::new({
vec![
StateButton::default()
StateButtonBuilder::new()
.with_text("Remember username")
.with_selector(remember_username_selector)
.with_event(remember_username_action)
.with_transparent_background()
.build()
.wrap(),
StateButton::default()
StateButtonBuilder::new()
.with_text("Remember password")
.with_selector(remember_password_selector)
.with_event(remember_password_action)
.with_transparent_background()
.build()
.wrap(),
]
})
Expand Down
3 changes: 2 additions & 1 deletion src/interface/windows/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ impl<TITLE, CLOSABLE, CLASS, BACKGROUND, THEME>
WindowBuilder<TITLE, CLOSABLE, CLASS, With<SizeBound>, With<Vec<ElementCell>>, BACKGROUND, THEME>
{
/// Take the builder and turn it into a [`Window`].
///
/// NOTE: This method is only available if
/// [`with_size_bound`](Self::with_size_bound) and
/// [`with_elements`](Self::with_elements) have been called on the builder.
Expand All @@ -147,7 +148,7 @@ impl<TITLE, CLOSABLE, CLASS, BACKGROUND, THEME>
let mut elements = elements.take();

if closable {
let close_button = CloseButton::default().wrap();
let close_button = CloseButtonBuilder::new().build().wrap();
elements.insert(0, close_button);
}

Expand Down
10 changes: 6 additions & 4 deletions src/interface/windows/debug/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@ impl<const N: usize> PrototypeWindow for PacketWindow<N> {
.with_width_bound(dimension_bound!(33.33%))
.build()
.wrap(),
StateButton::default()
StateButtonBuilder::new()
.with_text("Show pings")
.with_selector(self.show_pings.selector())
.with_event(self.show_pings.toggle_action())
.with_width(dimension_bound!(33.33%))
.with_width_bound(dimension_bound!(33.33%))
.build()
.wrap(),
StateButton::default()
StateButtonBuilder::new()
.with_text("Update")
.with_selector(self.update.selector())
.with_event(self.update.toggle_action())
.with_width(dimension_bound!(!))
.with_width_bound(dimension_bound!(!))
.build()
.wrap(),
ScrollView::new(elements, size_bound!(100%, ? < super)).wrap(),
];
Expand Down
10 changes: 6 additions & 4 deletions src/interface/windows/debug/profiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ impl PrototypeWindow for ProfilerWindow {
.with_width(dimension_bound!(150))
.with_event(Box::new(Vec::new))
.wrap(),
StateButton::default()
StateButtonBuilder::new()
.with_text("Always update")
.with_selector(self.always_update.selector())
.with_event(self.always_update.toggle_action())
.with_width(dimension_bound!(150))
.with_width_bound(dimension_bound!(150))
.build()
.wrap(),
StateButton::default()
StateButtonBuilder::new()
.with_text("Halt")
.with_selector(|_: &StateProvider| is_profiler_halted())
.with_event(Box::new(toggle_halting))
.with_width(dimension_bound!(150))
.with_width_bound(dimension_bound!(150))
.build()
.wrap(),
ElementWrap::wrap(FrameView::new(
self.always_update.new_remote(),
Expand Down
Loading

0 comments on commit ca66635

Please sign in to comment.