Skip to content

Instantly share code, notes, and snippets.

@kellpossible
Created June 6, 2021 01:28
Show Gist options
  • Save kellpossible/ad62790ed12647331b4d0373b509509f to your computer and use it in GitHub Desktop.
Save kellpossible/ad62790ed12647331b4d0373b509509f to your computer and use it in GitHub Desktop.
Rust Gui Abstraction
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use core::marker::PhantomData;
#[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Hash, Debug)]
struct CallbackId(u32);
impl CallbackId {
pub fn none() -> Self {
CallbackId(0)
}
}
impl Default for CallbackId {
fn default() -> Self {
Self::none()
}
}
#[derive(Serialize, Deserialize, Debug)]
struct SelectionIndex(u32);
#[derive(Serialize, Deserialize, Debug)]
#[non_exhaustive]
enum OnChangeValue {
Text(String),
Checkbox(CheckboxState),
Selection(SelectionIndex),
}
#[derive(Serialize, Deserialize, Debug)]
struct OnChangeEvent {
value: OnChangeValue,
}
#[derive(Serialize, Deserialize, Debug)]
#[non_exhaustive]
enum Event {
OnChange(OnChangeEvent),
OnClick,
}
#[derive(Serialize, Deserialize, Debug)]
struct CallbackEvent {
callback: CallbackId,
event: Event,
}
#[derive(Serialize, Deserialize, Debug)]
enum TextEditorType {
Password,
OneLine,
MultiLine,
}
#[derive(Serialize, Deserialize, Debug)]
struct TextEditor {
on_change: CallbackId,
editor_type: TextEditorType,
}
#[derive(Serialize, Deserialize, Debug)]
struct RadioEditor {
on_change: CallbackId,
}
#[derive(Serialize, Deserialize, Debug)]
enum CheckboxState {
Checked,
Unchecked
}
#[derive(Serialize, Deserialize, Debug)]
struct CheckboxEditor {
onchange: CallbackId,
}
#[derive(Serialize, Deserialize, Debug)]
enum FieldEditor {
Text(TextEditor),
Radio(RadioEditor),
}
impl From<TextEditor> for FieldEditor {
fn from(text_editor: TextEditor) -> Self {
Self::Text(text_editor)
}
}
#[derive(Serialize, Deserialize, Debug)]
struct FormField {
label: Option<String>,
editor: FieldEditor,
enabled: bool,
}
#[derive(Serialize, Deserialize, Debug)]
struct Button {
title: String,
on_click: CallbackId,
enabled: bool,
}
#[derive(Serialize, Deserialize, Debug)]
enum FormElement {
Field(FormField),
Button(Button),
Horizontal(Vec<FormElement>),
}
#[derive(Serialize, Deserialize, Debug)]
struct Form {
elements: Vec<FormElement>,
}
#[derive(Serialize, Deserialize, Debug)]
enum PageContents {
Form(Form),
}
#[derive(Serialize, Deserialize, Debug)]
struct Page {
title: String,
contents: PageContents,
}
#[derive(Default)]
struct CallbackRegister<D: Default> {
prev_id: CallbackId,
callbacks: HashMap<CallbackId, Box<dyn Fn(Event, &mut D)>>,
data: PhantomData<D>,
}
impl<D: Default> CallbackRegister<D> {
pub fn new() -> Self {
Self::default()
}
fn next_id(&self) -> CallbackId {
CallbackId(self.prev_id.0 + 1)
}
pub fn call(&self, callback: CallbackId, event: Event, data: &mut D) {
self.callbacks.get(&callback).unwrap()(event, data)
}
fn assign_id(&mut self, callback: Box<dyn Fn(Event, &mut D)>) -> CallbackId {
let next_id = self.next_id();
self.callbacks.insert(next_id, callback);
self.prev_id = next_id;
next_id
}
pub fn on_click(&mut self, callback: impl Fn(&mut D) + 'static) -> CallbackId {
let on_click_callback = Box::new(move |event: Event, data: &mut D| {
match event {
Event::OnClick => {
callback(data)
}
_ => eprintln!("Unexpected event"),
}
});
self.assign_id(on_click_callback)
}
pub fn on_change(&mut self, callback: impl Fn(OnChangeEvent, &mut D) + 'static) -> CallbackId {
let on_change_callback = Box::new(move |event: Event, data: &mut D| {
match event {
Event::OnChange(on_change_event) => {
callback(on_change_event, data)
}
_ => eprintln!("Unexpected event"),
}
});
self.assign_id(on_change_callback)
}
}
#[derive(Default, Debug)]
struct Data {
password: String
}
impl Data {
pub fn is_valid(&self) -> bool {
!self.password.is_empty()
}
}
fn login_page(data: &Data, callbacks: &mut CallbackRegister<Data>) -> Page {
let password_callback: CallbackId = callbacks.on_change(|event: OnChangeEvent, data: &mut Data| {
match event.value {
OnChangeValue::Text(text) => {
println!("The password changed to: {:?}", text);
data.password = text;
}
unexpected => {
eprintln!("Unexpected event value: {:?}", unexpected)
}
}
});
let login_callback: CallbackId = callbacks.on_click(|_data: &mut Data| {
println!("The login button was clicked")
});
Page {
title: "Login".into(),
contents: PageContents::Form(Form {
elements: vec![
FormElement::Field(FormField {
label: Some("Password".into()),
editor: TextEditor {
editor_type: TextEditorType::Password,
on_change: password_callback,
}.into(),
enabled: true,
}),
FormElement::Button(Button {
title: "Login".into(),
on_click: login_callback,
enabled: data.is_valid(),
})
]
})
}
}
struct Gui {
data: Data,
callbacks: CallbackRegister<Data>,
}
impl Gui {
pub fn new() -> Self {
Self {
data: Default::default(),
callbacks: CallbackRegister::new(),
}
}
pub fn process_event(&mut self, callback_event: CallbackEvent) {
self.callbacks.call(callback_event.callback, callback_event.event, &mut self.data)
}
pub fn render(&mut self) {
println!("Render: {:#?}", login_page(&self.data, &mut self.callbacks));
}
}
fn main() {
let mut gui = Gui::new();
gui.render();
gui.process_event(CallbackEvent {
callback: CallbackId(1),
event: Event::OnChange({
OnChangeEvent {
value: OnChangeValue::Text("hello".into())
}
}),
});
gui.render();
gui.process_event(CallbackEvent {
callback: CallbackId(2),
event: Event::OnClick,
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment