Skip to content

Instantly share code, notes, and snippets.

@tesfabpel
Last active February 18, 2023 10:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tesfabpel/9a00807cfe8042cd01e623520bcb875f to your computer and use it in GitHub Desktop.
Save tesfabpel/9a00807cfe8042cd01e623520bcb875f to your computer and use it in GitHub Desktop.
Rust GUI framework design
mod core {
use generational_arena::{Arena, Index};
use qcell::LCell;
use qcell::LCellOwner;
use queues::Queue;
use std::marker::PhantomData;
use std::rc::Rc;
#[derive(PartialEq)]
pub enum KeyCode {
N,
}
pub trait Bundle {
fn name(&self) -> &Name;
}
pub struct EmptyBundle {
pub name: Name,
}
impl Bundle for EmptyBundle {
fn name(&self) -> &Name {
&self.name
}
}
#[derive(Clone)]
pub struct Handle<'a>(Rc<LCell<'a, Index>>);
#[derive(Clone)]
pub struct Name(pub Option<String>);
impl Default for Name {
fn default() -> Self {
Self(None)
}
}
pub struct Context {
root: Index,
entities: Arena<(Name, Box<dyn Bundle>)>,
}
impl Context {
pub fn new() -> Self {
let mut arena: Arena<(Name, Box<(dyn Bundle + 'static)>)> = Arena::new();
let root = arena.insert((Name(None), Box::new(EmptyBundle { name: Name(None) })));
Self {
root,
entities: arena,
}
}
pub fn create_entity(&mut self, b: impl Bundle + 'static) -> Index {
let name = b.name().clone();
self.entities.insert((name, Box::new(b)))
}
pub fn remove_entity(&mut self, i: Index) -> bool {
if i == self.root {
return false;
}
self.entities.remove(i).is_some()
}
pub fn effectful<F>(&mut self, fun: F)
where
F: for<'scope> FnOnce(DomCommands<'_, 'scope>),
{
LCellOwner::scope(|owner| {
let cmd = DomCommands {
ctx: self,
owner,
queue: Queue::new(),
handles: Vec::new(),
};
fun(cmd);
// process `cmd`
});
}
}
enum HandleCommand {
Created,
Removed,
}
pub struct DomCommands<'a, 'scope> {
ctx: &'a mut Context,
owner: LCellOwner<'scope>,
queue: Queue<DomCommand>,
handles: Vec<(Handle<'scope>, HandleCommand)>,
}
impl<'a, 'scope> DomCommands<'a, 'scope> {
pub fn root_entity(&self) -> Handle<'scope> {
let root = self.ctx.root;
Handle(Rc::new(self.owner.cell(root)))
}
pub fn find_entity_by_name(&self, name: &str) -> Handle<'scope> {
let h = self.try_find_entity_by_name(name);
match h {
None => panic!(),
Some(h) => h,
}
}
pub fn try_find_entity_by_name(&self, name: &str) -> Option<Handle<'scope>> {
for ent in self.ctx.entities.iter() {
if let Name(Some(ref ent_name)) = ent.1 .0 {
let ent_name = ent_name.as_str();
if name == ent_name {
return Some(Handle(Rc::new(self.owner.cell(ent.0))));
}
}
}
None
}
pub fn spawn(&mut self, b: impl Bundle + 'static) -> Option<Handle<'scope>> {
if let Name(Some(ref name)) = b.name() {
let s = name.as_str();
let h = self.try_find_entity_by_name(s);
if h.is_some() {
return None;
}
}
let id = self.ctx.create_entity(b);
let h = Handle(Rc::new(self.owner.cell(id)));
self.handles.push((h.clone(), HandleCommand::Created));
Some(h)
}
pub fn insert_entity_after(&mut self, entity: &Handle, target: &Handle) {}
pub fn append_entity(&mut self, entity: &Handle, container: &Handle) {}
}
#[derive(Clone)]
pub enum DomCommand {
Add(),
Remove(),
}
pub struct Color;
impl Color {
pub fn from_hex(hex: u32) -> Color {
Color
}
}
impl Default for Color {
fn default() -> Self {
Color
}
}
}
mod ui {
use crate::core::Name;
#[derive(Default)]
pub struct TextBundle {
pub name: Name,
pub text: String,
pub color: crate::core::Color,
}
impl crate::core::Bundle for TextBundle {
fn name(&self) -> &Name {
&self.name
}
}
pub struct KeyPress {
pub key_code: crate::core::KeyCode,
}
pub enum Message {
KeyPress(KeyPress),
}
pub trait UiComponent {
fn on_message(&mut self, ctx: &mut crate::core::Context, msg: &Message);
}
}
use std::marker::PhantomData;
use crate::core::Context;
use crate::core::DomCommands;
use crate::core::Handle;
use crate::core::KeyCode;
use crate::core::{Color, Name};
use crate::ui::KeyPress;
use crate::ui::Message;
use crate::ui::TextBundle;
use crate::ui::UiComponent;
struct MainForm;
const ENT_NAME_LBL_HELLO: &str = "lbl_hello";
const ENT_NAME_LBL_WORLD: &str = "lbl_world";
impl UiComponent for MainForm {
fn on_message(&mut self, ctx: &mut Context, msg: &Message) {
match msg {
Message::KeyPress(ref kp) => self.on_key_press(ctx, kp),
}
}
}
impl MainForm {
fn on_key_press(&mut self, ctx: &mut Context, ev: &KeyPress) {
if ev.key_code != KeyCode::N {
return;
}
//let mut aaa = Handle {
// _pd: PhantomData
//};
// ctx.effectful takes ctx as `&mut self` and executes the FnOnce
// with a purposely created `DomCommands`
ctx.effectful(|mut dom: DomCommands| {
let root = dom.root_entity();
//dom.try_find_entity_by_name(ENT_NAME_LBL_HELLO);
let lbl_hello = dom.find_entity_by_name(ENT_NAME_LBL_HELLO);
// Handles are temporary, tied to the lifetime of `dom`
// which gets created for every `ctx.effectful` call
let lbl_world = dom
.spawn(TextBundle {
name: Name(Some(ENT_NAME_LBL_WORLD.to_string())),
text: ", World!".to_string(),
color: Color::from_hex(0xFF0000),
})
.unwrap();
dom.insert_entity_after(&lbl_world, &lbl_hello);
for _ in 0..10 {
let handle = dom
.spawn(TextBundle {
name: Name(None),
text: "Hello, World!".to_string(),
..Default::default()
})
.unwrap();
dom.append_entity(&handle, &root);
}
//aaa = lbl_hello;
// only at the end of `effectful`, the commands are
// really applied to the "DOM".
// until then, entities exist only with their
// temporary handle.
});
ctx.effectful(|dom: DomCommands| {
//...
});
}
}
fn main() {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment