Skip to content

Commit

Permalink
Add missing files
Browse files Browse the repository at this point in the history
  • Loading branch information
vE5li committed Mar 5, 2024
1 parent 21eeb1b commit 0e5f0fb
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 0 deletions.
126 changes: 126 additions & 0 deletions src/interface/elements/miscellanious/input/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use std::fmt::Display;

use super::{EnterAction, InputField};
use crate::interface::builder::{Set, Unset};
use crate::interface::*;

/// Type state [`InputField`] 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 InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> {
input_state: STATE,
ghost_text: TEXT,
enter_action: ACTION,
length: usize,
hidden: bool,
width_bound: Option<DimensionBound>,
marker: PhantomData<(LENGTH, HIDDEN, WIDTH)>,
}

impl InputFieldBuilder<Unset, Unset, Unset, Unset, Unset, Unset> {
pub fn new() -> Self {
Self {
input_state: Unset,
ghost_text: Unset,
enter_action: Unset,
length: 0,
hidden: false,
width_bound: None,
marker: PhantomData,
}
}
}

impl<TEXT, ACTION, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<Unset, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> {
pub fn with_state(self, state: TrackedState<String>) -> InputFieldBuilder<TrackedState<String>, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> {
InputFieldBuilder {
input_state: state,
..self
}
}
}

impl<STATE, ACTION, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<STATE, Unset, ACTION, LENGTH, HIDDEN, WIDTH> {
pub fn with_ghost_text<TEXT: Display + 'static>(
self,
ghost_text: TEXT,
) -> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, WIDTH> {
InputFieldBuilder { ghost_text, ..self }
}
}

impl<STATE, TEXT, LENGTH, HIDDEN, WIDTH> InputFieldBuilder<STATE, TEXT, Unset, LENGTH, HIDDEN, WIDTH> {
pub fn with_enter_action(
self,
enter_action: impl FnMut() -> Vec<ClickAction> + 'static,
) -> InputFieldBuilder<STATE, TEXT, EnterAction, LENGTH, HIDDEN, WIDTH> {
InputFieldBuilder {
enter_action: Box::new(enter_action),
..self
}
}
}

impl<STATE, TEXT, ACTION, HIDDEN, WIDTH> InputFieldBuilder<STATE, TEXT, ACTION, Unset, HIDDEN, WIDTH> {
pub fn with_length(self, length: usize) -> InputFieldBuilder<STATE, TEXT, ACTION, Set, HIDDEN, WIDTH> {
InputFieldBuilder {
length,
marker: PhantomData,
..self
}
}
}

impl<STATE, TEXT, ACTION, LENGTH, WIDTH> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, Unset, WIDTH> {
pub fn with_hidden(self) -> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, Set, WIDTH> {
InputFieldBuilder {
hidden: true,
marker: PhantomData,
..self
}
}
}

impl<STATE, TEXT, ACTION, LENGTH, HIDDEN> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, Unset> {
pub fn with_width_bound(self, width_bound: DimensionBound) -> InputFieldBuilder<STATE, TEXT, ACTION, LENGTH, HIDDEN, Set> {
InputFieldBuilder {
width_bound: Some(width_bound),
marker: PhantomData,
..self
}
}
}

impl<TEXT, HIDDEN, WIDTH> InputFieldBuilder<TrackedState<String>, TEXT, EnterAction, Set, HIDDEN, WIDTH>
where
TEXT: Display + 'static,
{
/// Take the builder and turn it into a [`InputField`].
///
/// NOTE: This method is only available if [`with_state`](Self::with_state),
/// [`with_ghost_text`](Self::with_ghost_text),
/// [`with_enter_action`](Self::with_enter_action),
/// and [`with_length`](Self::with_length) have been called on the builder.
pub fn build(self) -> InputField<TEXT> {
let Self {
input_state,
ghost_text,
enter_action,
length,
hidden,
width_bound,
..
} = self;

InputField {
input_state,
ghost_text,
enter_action,
length,
hidden,
width_bound,
state: Default::default(),
}
}
}
159 changes: 159 additions & 0 deletions src/interface/elements/miscellanious/input/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
mod builder;

use std::fmt::Display;

use procedural::dimension_bound;

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

/// Local type alias to simplify the builder.
type EnterAction = Box<dyn FnMut() -> Vec<ClickAction>>;

pub struct InputField<TEXT: Display + 'static> {
input_state: TrackedState<String>,
ghost_text: TEXT,
enter_action: EnterAction,
length: usize,
hidden: bool,
width_bound: Option<DimensionBound>,
state: ElementState,
}

impl<TEXT: Display + 'static> InputField<TEXT> {
fn remove_character(&mut self) -> Vec<ClickAction> {
self.input_state.with_mut(|input_state, changed| {
if input_state.is_empty() {
return Vec::new();
}

input_state.pop();
changed();

vec![ClickAction::ChangeEvent(ChangeEvent::RENDER_WINDOW)]
})
}

fn add_character(&mut self, character: char) -> Vec<ClickAction> {
self.input_state.with_mut(|input_state, changed| {
if input_state.len() >= self.length {
return Vec::new();
}

input_state.push(character);
changed();

vec![ClickAction::ChangeEvent(ChangeEvent::RENDER_WINDOW)]
})
}
}

impl<TEXT: Display + 'static> Element for InputField<TEXT> {
fn get_state(&self) -> &ElementState {
&self.state
}

fn get_state_mut(&mut self) -> &mut ElementState {
&mut self.state
}

fn resolve(&mut self, placement_resolver: &mut PlacementResolver, _interface_settings: &InterfaceSettings, theme: &InterfaceTheme) {
let size_bound = self
.width_bound
.as_ref()
.unwrap_or(&dimension_bound!(100%))
.add_height(theme.input.height_bound);

self.state.resolve(placement_resolver, &size_bound);
}

fn hovered_element(&self, mouse_position: ScreenPosition, mouse_mode: &MouseInputMode) -> HoverInformation {
match mouse_mode {
MouseInputMode::None => self.state.hovered_element(mouse_position),
_ => HoverInformation::Missed,
}
}

fn left_click(&mut self, _update: &mut bool) -> Vec<ClickAction> {
vec![ClickAction::FocusElement]
}

fn input_character(&mut self, character: char) -> Vec<ClickAction> {
match character {
'\u{8}' | '\u{7f}' => self.remove_character(),
'\r' => (self.enter_action)(),
character => self.add_character(character),
}
}

fn render(
&self,
render_target: &mut <InterfaceRenderer as Renderer>::Target,
renderer: &InterfaceRenderer,
_state_provider: &StateProvider,
interface_settings: &InterfaceSettings,
theme: &InterfaceTheme,
parent_position: ScreenPosition,
screen_clip: ScreenClip,
hovered_element: Option<&dyn Element>,
focused_element: Option<&dyn Element>,
_mouse_mode: &MouseInputMode,
_second_theme: bool,
) {
let mut renderer = self
.state
.element_renderer(render_target, renderer, interface_settings, parent_position, screen_clip);

let input_state = self.input_state.borrow();
let is_hovererd = self.is_element_self(hovered_element);
let is_focused = self.is_element_self(focused_element);
let text_offset = theme.input.text_offset.get();

let text = if input_state.is_empty() && !is_focused {
self.ghost_text.to_string()
} else if self.hidden {
input_state.chars().map(|_| '*').collect()
} else {
input_state.clone()
};

let background_color = if is_hovererd {
theme.input.hovered_background_color.get()
} else if is_focused {
theme.input.focused_background_color.get()
} else {
theme.input.background_color.get()
};

let text_color = if input_state.is_empty() && !is_focused {
theme.input.ghost_text_color.get()
} else if is_focused {
theme.input.focused_text_color.get()
} else {
theme.input.text_color.get()
};

renderer.render_background(theme.input.corner_radius.get(), background_color);
renderer.render_text(&text, text_offset, text_color, theme.input.font_size.get());

if is_focused {
let cursor_offset = (text_offset.left + theme.input.cursor_offset.get()) * interface_settings.scaling.get()
+ renderer.get_text_dimensions(&text, theme.input.font_size.get(), f32::MAX).x;

let cursor_position = ScreenPosition::only_left(cursor_offset);
let cursor_size = ScreenSize {
width: theme.input.cursor_width.get(),
height: self.state.cached_size.height,
};

renderer.render_rectangle(
cursor_position,
cursor_size,
CornerRadius::default(),
theme.input.text_color.get(),
);
}
}
}

0 comments on commit 0e5f0fb

Please sign in to comment.