scheme support refactoring

This commit is contained in:
chayleaf 2024-09-04 07:35:50 +07:00
parent 01e0871111
commit dfcc06fb31
Signed by: chayleaf
GPG key ID: 78171AD46227E68E
6 changed files with 694 additions and 594 deletions

View file

@ -2,7 +2,7 @@ use std::{marker::PhantomData, mem, slice::ChunksExactMut};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PixelFormat {
Argb8888Le,
}

View file

@ -1,12 +1,15 @@
use std::{collections::HashMap, time::Instant};
use std::{collections::HashMap, fmt::Debug};
use crate::{
graphics::{self, LazyCanvas},
image::PixelFormat,
script::{Engine, Value},
script::{Engine, Func, ValTrait, Value},
text::FontDb,
};
mod script;
#[derive(Clone, Debug)]
pub struct Popup {
left: i32,
top: i32,
@ -21,6 +24,7 @@ pub struct PopupInfo<'b> {
font_db: &'b mut FontDb,
}
#[derive(Clone, Debug)]
pub struct Class<E: Engine> {
pub supers: Vec<String>,
pub methods: HashMap<String, E::Func>,
@ -43,18 +47,24 @@ pub struct Keyboard<E: Engine> {
fmt: PixelFormat,
pub(crate) font_db: FontDb,
is_first_draw: bool,
config_handlers: HashMap<
String,
Vec<(
usize,
Box<dyn 'static + FnMut(&Self, &str, &toml_edit::Value)>,
)>,
>,
timers: Vec<(Instant, Box<dyn 'static + FnOnce(&Self)>)>,
config_handler_id: usize,
pub(crate) user_data: HashMap<String, Value<E>>,
pub(crate) classes: HashMap<String, Class<E>>,
pub(crate) env: Option<crate::ScriptEnv<E>>,
pub(crate) user_data: HashMap<String, Value<Self>>,
pub(crate) classes: HashMap<String, Class<Self>>,
/// SAFETY: unsafe
engine: E,
}
impl<E: Engine> Debug for Keyboard<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Keyboard")
.field("layout", &self.layout)
.field("popup", &self.popup)
.field("width", &self.width)
.field("height", &self.height)
.field("fmt", &self.fmt)
.field("font_db", &self.font_db)
.field("is_first_draw", &self.is_first_draw)
.field("classes", &self.classes)
.finish_non_exhaustive()
}
}
impl<'b> PopupInfo<'b> {
@ -127,15 +137,9 @@ pub struct DamageInfo {
pub height: u32,
}
impl<E: Engine> Default for Keyboard<E> {
fn default() -> Self {
Self::new()
}
}
impl<E: Engine> Keyboard<E> {
pub fn new() -> Self {
Self {
impl<E: 'static + Engine> Keyboard<E> {
pub fn new(engine: E) -> Self {
let mut ret = Self {
width: 720,
height: 480,
font_db: Default::default(),
@ -143,16 +147,16 @@ impl<E: Engine> Keyboard<E> {
layout: None,
popup: None,
is_first_draw: true,
config_handlers: HashMap::new(),
config_handler_id: 0,
timers: Vec::new(),
user_data: HashMap::new(),
classes: HashMap::new(),
env: None,
}
engine,
};
script::init_builtins(&mut ret);
// ret.layout = Some(ret.create_layout());
// ret
ret
}
}
impl<E: Engine> Keyboard<E> {
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
@ -189,24 +193,6 @@ impl<E: Engine> Keyboard<E> {
is_first_draw: true,
});*/
}
pub fn _add_settings_handler(
&mut self,
key: &str,
handler: impl 'static + FnMut(&Self, &str, &toml_edit::Value),
) -> usize {
let id = self.config_handler_id;
self.config_handler_id += 1;
self.config_handlers
.entry(key.to_owned())
.or_default()
.push((id, Box::new(handler)));
id
}
pub fn _remove_settings_handler(&mut self, key: &str, id: usize) {
if let Some(x) = self.config_handlers.get_mut(key) {
x.retain(|(i, _)| id != *i);
}
}
pub fn hide(&mut self) {
self.popup = None;
}
@ -235,10 +221,7 @@ impl<E: Engine> Keyboard<E> {
.and_then(|cls| cls.methods.get_mut("kbd/width-changed"))
.cloned()
{
let mut env = self.env.take().unwrap();
env.call(self, &func, vec![Value::Symbol("en".into())])
.unwrap();
self.env = Some(env);
func.call(self, vec![Value::Symbol("en".into())]).unwrap();
}
self.is_first_draw = false;
if let Some(layout) = &mut self.layout {
@ -361,3 +344,123 @@ impl<E: Engine> Keyboard<E> {
layout
}*/
}
#[repr(transparent)]
pub struct ValueWrapper<E: Engine>(E::Value);
impl<E: Engine> ValTrait for ValueWrapper<E> {
type Engine = Keyboard<E>;
fn from_val2(val: Value<Self::Engine>, engine: &mut Self::Engine) -> Self {
Self(E::Value::from_val2(val.convert(), &mut engine.engine))
}
fn from_val(val: Value<Self::Engine>) -> Self {
Self(E::Value::from_val(val.convert()))
}
fn to_val(&self) -> Value<Self::Engine> {
self.0.to_val().convert()
}
fn to_val2(&self, engine: &Self::Engine) -> Value<Self::Engine> {
self.0.to_val2(&engine.engine).convert()
}
}
#[repr(transparent)]
#[derive(Debug)]
pub struct FuncWrapper<E: Engine>(E::Func);
impl<E: Engine> Clone for FuncWrapper<E>
where
E::Func: Clone,
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<E: Engine> Ord for FuncWrapper<E>
where
E::Func: Ord,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<E: Engine> Eq for FuncWrapper<E> where E::Func: Eq {}
impl<E: Engine> PartialOrd for FuncWrapper<E>
where
E::Func: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.0.cmp(&other.0))
}
}
impl<E: Engine> PartialEq for FuncWrapper<E>
where
E::Func: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl<E: Engine> Func for FuncWrapper<E> {
type BasicValue = <E::Func as Func>::BasicValue;
fn from_basic_value(v: Self::BasicValue) -> Self {
Self(E::Func::from_basic_value(v))
}
fn into_basic_value(self) -> Self::BasicValue {
self.0.into_basic_value()
}
type Engine = Keyboard<E>;
fn call(
&self,
engine: &mut Self::Engine,
args: Vec<Value<Self::Engine>>,
) -> Result<Value<Self::Engine>, <Self::Engine as Engine>::Error> {
Ok(self
.0
.call(
&mut engine.engine,
args.into_iter().map(Value::convert).collect(),
)?
.convert())
}
fn unref(&mut self, engine: &mut Self::Engine) {
self.0.unref(&mut engine.engine);
}
fn add_ref(&mut self, engine: &mut Self::Engine) {
self.0.add_ref(&mut engine.engine);
}
}
impl<E: Engine> Engine for Keyboard<E> {
type Value = ValueWrapper<E>;
type Error = E::Error;
type Func = FuncWrapper<E>;
fn eval_t(&mut self, text: &str) -> Result<Value<Self>, Self::Error> {
Ok(self.engine.eval_t(text)?.convert())
}
fn gc(&mut self) {
self.engine.gc();
}
fn register(&mut self, name: &str, val: Value<Self>) {
self.engine.register(name, val.convert())
}
#[inline(always)]
fn register_func(
&mut self,
name: &'static str,
func: impl 'static + Fn(&mut Self, Vec<Value<Self>>) -> Result<Value<Self>, Self::Error>,
) {
self.engine.register_func(name, move |a: &mut E, b| {
Ok(func(
// SAFETY: as long as engine isn't used directly by the Keyboard,
// and self is used as the engine itself, this is fine
unsafe {
let ofs = std::mem::offset_of!(Self, engine);
let ptr: *mut Self = ((a as *mut E) as *mut Self).byte_sub(ofs);
&mut *ptr
},
// SAFETY: should have the same layout,
// because FuncWrapper is repr(transparent)
unsafe { std::mem::transmute::<Vec<Value<E>>, Vec<Value<Self>>>(b) },
)?
.convert())
})
}
}

469
src/keyboard/script.rs Normal file
View file

@ -0,0 +1,469 @@
use crate::{
graphics, image,
script::{Args, Engine, ErrorTrait, Func, Value},
};
use super::Keyboard;
fn register<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 3], _>([cls, name, func]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/register"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{cls:?}"),
));
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{name:?}"),
));
};
let Value::Func(mut func) = func else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"function",
&format!("{func:?}"),
));
};
func.add_ref(vm);
println!("register {cls:?} {name:?} {func:?}");
vm.classes
.entry(cls.into_owned())
.or_default()
.methods
.insert(name.into_owned(), func);
println!("done");
Ok(Value::Null)
}
fn class_define<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 2], _>([cls, supers]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/class-define"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"symbol",
&format!("{cls:?}"),
));
};
let supers = match supers {
Value::Null => vec![],
Value::RevArray(supers) => supers,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list",
&format!("{supers:?}"),
));
}
};
let supers = supers
.into_iter()
.rev()
.map(|sup| {
let Value::Symbol(sup) = sup else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list of symbols",
&format!("list containing {sup:?}"),
));
};
Ok(sup.into_owned())
})
.collect::<Result<Vec<_>, _>>()?;
println!("register {cls:?} {supers:?}");
vm.classes
.entry(cls.into_owned())
.or_default()
.supers
.extend(supers);
println!("done");
Ok(Value::Null)
}
fn call<E: Engine>(
vm: &mut Keyboard<E>,
mut args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
args.reverse();
let cls = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let name = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{cls:?}"),
));
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{name:?}"),
));
};
fn call_func<E: Engine>(
func: &mut E::Func,
engine: &mut E,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
func.call(engine, args)
}
let func = vm
.classes
.get_mut(cls.as_ref())
.and_then(|cls| cls.methods.get_mut(name.as_ref()).cloned());
if let Some(mut func) = func {
args.push(Value::Symbol(cls));
args.reverse();
call_func(&mut func, vm, args)
} else {
Err(ErrorTrait::from_str(
"kbd/call",
&format!("{cls}/{name} is not defined"),
))
}
}
fn set_user_data<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 2], _>([k, mut v]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-user-data"));
};
let Value::Symbol(k) = k else {
return Err(ErrorTrait::invalid_argt(
"kbd/set-user-data",
"symbol",
&format!("{k:?}"),
));
};
println!("setting user data {k:?} to {v:?}");
if let Some(mut val) = vm.user_data.insert(k.into_owned(), v.clone()) {
// FIXME: theoretically this can unref data that's referenced elsewhere
val.unref(vm);
}
v.add_ref(vm);
Ok(Value::Null)
}
fn get_user_data<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([k]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/get-user-data"));
};
let Value::Symbol(k) = k else {
return Err(ErrorTrait::invalid_argt(
"kbd/get-user-data",
"symbol",
&format!("{k:?}"),
));
};
println!("getting user data {k:?}");
Ok(vm
.user_data
.get(k.as_ref())
.cloned()
.unwrap_or(Value::Bool(false)))
}
fn add_to_i64<E: Engine>(name: &str, a: Value<E>, b: Value<E>) -> Result<i64, E::Error> {
Ok(match (a, b) {
(Value::Int(a), Value::Int(b)) => a.saturating_add(b),
(Value::Float(a), Value::Float(b)) => (a + b) as i64,
(Value::Int(a), Value::Float(b)) => (a as f64 + b) as i64,
(Value::Float(a), Value::Int(b)) => (a + b as f64) as i64,
x => {
return Err(E::Error::invalid_argt(
name,
"int or float",
&format!("{:?}", x.1),
))
}
})
}
fn to_i64<E: Engine>(name: &str, x: Value<E>) -> Result<i64, E::Error> {
add_to_i64(name, x, Value::Int(0))
}
fn make_text<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 5], _>([xy, wh, text, font, font_size]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-text"));
};
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{wh:?}"),
));
};
let Value::String(text) = text else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{text:?}"),
));
};
let Value::String(font) = font else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font:?}"),
));
};
let font_size = match font_size {
Value::Int(x) => x as f64,
Value::Float(x) => x,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font_size:?}"),
))
}
};
let Some(font_handle) = vm.font_db.font_handle(&font) else {
return Err(ErrorTrait::from_str("kbd/make-text", "font not found"));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-text", x.clone())?;
let y1 = to_i64("kbd/make-text", y.clone())?;
let x_w = add_to_i64("kbd/make-text", x, w)?;
let y_h = add_to_i64("kbd/make-text", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let halign = graphics::Alignment::Middle;
let valign = graphics::Alignment::Middle;
let direction = swash::shape::Direction::LeftToRight;
let script = swash::text::Script::Latin;
let color = image::ColorRgba([255, 0, 0, 255]);
// [xy, wh, text, font, font_size]
// println!("rendering text {text} of size {w}/{h} at {x}/{y} with font {font} {font_size}, color {color:?}, alignment {halign:?}/{valign:?}, script {script:?}, direction {direction:?}");
let text = graphics::WidgetEnum::Text(graphics::Text {
direction,
font_size: font_size as f32,
font_handle,
color,
halign,
valign,
script,
text: text.into(),
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
height: h as u32,
width: w as u32,
},
});
Value::from_serde(&text).map_err(|err| ErrorTrait::from_str("kbd/make-text", &err.to_string()))
}
fn make_rect<E: Engine>(
_vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let mut args = args.into_iter();
let Some(xy) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let Some(wh) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let mut round = 0.0f64;
while let Some(arg) = args.next() {
match arg {
Value::Symbol(x) if x == ":round-corners" => {
round = 1.0;
if let Some(arg) = args.next() {
let Value::Float(arg) = arg else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"float",
&format!("{arg:?}"),
));
};
round = arg;
}
}
x => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"make-rect keyword arguments",
&format!("{x:?}"),
))
}
}
}
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{wh:?}"),
));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-rect", x.clone())?;
let y1 = to_i64("kbd/make-rect", y.clone())?;
let x_w = add_to_i64("kbd/make-rect", x, w)?;
let y_h = add_to_i64("kbd/make-rect", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let color = image::ColorRgba([0, 0, 0, 255]);
// [xy, wh, text, font, font_size]
println!("rendering rect of size {w}/{h} at {x}/{y} with rounding {round}, color {color:?}");
let rect = graphics::WidgetEnum::Shape(graphics::SimpleShape {
col: color,
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
width: w as u32,
height: h as u32,
},
round_corners_ratio: round as f32,
});
Value::from_serde(&rect).map_err(|err| ErrorTrait::from_str("kbd/make-rect", &err.to_string()))
}
fn make_layout<E: Engine>(
_vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([items]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-layout"));
};
let Value::RevArray(items) = items else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list",
&format!("{items:?}"),
));
};
let mut layout = graphics::Layout::new();
for item in items.into_iter().rev() {
let Value::RevArray(mut item) = item else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list of lists",
&format!("list with {item:?}"),
));
};
let Some(Value::String(a)) = item.pop() else {
return Err(ErrorTrait::from_str(
"kbd/make-layout",
"each item's first element must be the item id",
));
};
let item = Value::RevArray(item)
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))?;
// println!("{item:#?}");
layout.add_child(item, &a, None);
}
Value::from_serde(&layout)
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))
}
fn width<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 0], _>([]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/width"));
};
println!("getting keyboard width");
Ok(Value::Int(vm.width.into()))
}
fn load_font<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([path]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/load-font"));
};
let Value::String(path) = path else {
return Err(ErrorTrait::invalid_argt(
"kbd/load-font",
"string",
&format!("{path:?}"),
));
};
println!("loading font {path:?}");
vm.font_db
.load_font(path)
.map(|()| Value::Null)
.map_err(|err| ErrorTrait::from_str("kbd/load-font", &err.to_string()))
}
fn set_layout<E: Engine>(
vm: &mut Keyboard<E>,
args: Args<Keyboard<E>>,
) -> Result<Value<Keyboard<E>>, <Keyboard<E> as Engine>::Error> {
let Ok::<[Value<_>; 1], _>([layout]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-layout"));
};
let layout: graphics::Layout = layout
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/set-layout", &err.to_string()))?;
vm.layout = Some(layout);
Ok(Value::Null)
}
pub fn init_builtins<E: 'static + Engine>(engine: &mut Keyboard<E>) {
const DEFS: &str = include_str!("../../defs.scm");
engine.eval_t(DEFS).unwrap();
engine.gc();
engine.register_func("kbd/register", register);
engine.register_func("kbd/class-define", class_define);
engine.register_func("kbd/call", call);
engine.register_func("kbd/set-user-data", set_user_data);
engine.register_func("kbd/get-user-data", get_user_data);
engine.register_func("kbd/make-text", make_text);
engine.register(":round-corners", Value::new_symbol(":round-corners"));
engine.register_func("kbd/make-rect", make_rect);
engine.register_func("kbd/make-layout", make_layout);
engine.register_func("kbd/width", width);
engine.register_func("kbd/load-font", load_font);
engine.register_func("kbd/set-layout", set_layout);
engine.gc();
}

View file

@ -1,7 +1,6 @@
#![allow(clippy::type_complexity)]
use std::{cell::RefCell, rc::Rc};
use script::{Engine, ErrorTrait, Func, Value};
use script::Engine;
#[cfg(feature = "wayland")]
pub use wayland_protocols_misc;
@ -17,509 +16,12 @@ mod text;
#[cfg(feature = "wayland")]
mod wayland;
#[derive(Default)]
struct ScriptEnv<E: Engine>(E, Rc<RefCell<Keyboard<E>>>);
impl<E: Engine> ScriptEnv<E> {
fn eval(&mut self, kbd: &mut Keyboard<E>, text: &str) -> Result<Value<E>, E::Error> {
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
let ret = self.0.eval_t(text);
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
ret
}
fn call(
&mut self,
kbd: &mut Keyboard<E>,
func: &E::Func,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
// work with kbd1
let ret = func.call(&mut self.0, args);
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
ret
}
}
fn new_script_env() -> ScriptEnv<impl Engine> {
let mut engine = script::create_engine();
const DEFS: &str = include_str!("../defs.scm");
let ctx: Rc<RefCell<Keyboard<_>>> = Rc::default();
engine.eval_t(DEFS).unwrap();
engine.gc();
let ctx1 = ctx.clone();
engine.register_func(
"kbd/register",
Box::new(move |vm, args| {
let Ok::<[Value<_>; 3], _>([cls, name, func]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/register"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{cls:?}"),
));
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{name:?}"),
));
};
let Value::Func(mut func) = func else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"function",
&format!("{func:?}"),
));
};
func.add_ref(vm);
println!("register {cls:?} {name:?} {func:?}");
(*ctx1)
.borrow_mut()
.classes
.entry(cls.into_owned())
.or_default()
.methods
.insert(name.into_owned(), func);
println!("done");
Ok(Value::Null)
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/class-define",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 2], _>([cls, supers]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/class-define"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"symbol",
&format!("{cls:?}"),
));
};
let supers = match supers {
Value::Null => vec![],
Value::RevArray(supers) => supers,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list",
&format!("{supers:?}"),
));
}
};
let supers = supers
.into_iter()
.rev()
.map(|sup| {
let Value::Symbol(sup) = sup else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list of symbols",
&format!("list containing {sup:?}"),
));
};
Ok(sup.into_owned())
})
.collect::<Result<Vec<_>, _>>()?;
println!("register {cls:?} {supers:?}");
(*ctx1)
.borrow_mut()
.classes
.entry(cls.into_owned())
.or_default()
.supers
.extend(supers);
println!("done");
Ok(Value::Null)
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/call",
Box::new(move |vm, mut args| {
args.reverse();
let cls = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let name = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{cls:?}"),
));
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{name:?}"),
));
};
fn call_func<E: Engine>(
func: &mut E::Func,
engine: &mut E,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
func.call(engine, args)
}
let func = (*ctx1)
.borrow_mut()
.classes
.get_mut(cls.as_ref())
.and_then(|cls| cls.methods.get_mut(name.as_ref()).cloned());
if let Some(mut func) = func {
args.push(Value::Symbol(cls));
args.reverse();
call_func(&mut func, vm, args)
} else {
Err(ErrorTrait::from_str(
"kbd/call",
&format!("{cls}/{name} is not defined"),
))
}
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/set-user-data",
Box::new(move |vm, args| {
let Ok::<[Value<_>; 2], _>([k, mut v]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-user-data"));
};
let Value::Symbol(k) = k else {
return Err(ErrorTrait::invalid_argt(
"kbd/set-user-data",
"symbol",
&format!("{k:?}"),
));
};
println!("setting user data {k:?} to {v:?}");
if let Some(mut val) = (*ctx1)
.borrow_mut()
.user_data
.insert(k.into_owned(), v.clone())
{
// FIXME: theoretically this can unref data that's referenced elsewhere
val.unref(vm);
}
v.add_ref(vm);
Ok(Value::Null)
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/get-user-data",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 1], _>([k]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/get-user-data"));
};
let Value::Symbol(k) = k else {
return Err(ErrorTrait::invalid_argt(
"kbd/get-user-data",
"symbol",
&format!("{k:?}"),
));
};
println!("getting user data {k:?}");
Ok((*ctx1)
.borrow()
.user_data
.get(k.as_ref())
.cloned()
.unwrap_or(Value::Bool(false)))
}),
);
fn add_to_i64<E: Engine>(name: &str, a: Value<E>, b: Value<E>) -> Result<i64, E::Error> {
Ok(match (a, b) {
(Value::Int(a), Value::Int(b)) => a.saturating_add(b),
(Value::Float(a), Value::Float(b)) => (a + b) as i64,
(Value::Int(a), Value::Float(b)) => (a as f64 + b) as i64,
(Value::Float(a), Value::Int(b)) => (a + b as f64) as i64,
x => {
return Err(E::Error::invalid_argt(
name,
"int or float",
&format!("{:?}", x.1),
))
}
})
}
fn to_i64<E: Engine>(name: &str, x: Value<E>) -> Result<i64, E::Error> {
add_to_i64(name, x, Value::Int(0))
}
let ctx1 = ctx.clone();
engine.register_func(
"kbd/make-text",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 5], _>([xy, wh, text, font, font_size]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-text"));
};
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{wh:?}"),
));
};
let Value::String(text) = text else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{text:?}"),
));
};
let Value::String(font) = font else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font:?}"),
));
};
let font_size = match font_size {
Value::Int(x) => x as f64,
Value::Float(x) => x,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font_size:?}"),
))
}
};
let Some(font_handle) = ctx1.borrow_mut().font_db.font_handle(&font) else {
return Err(ErrorTrait::from_str("kbd/make-text", "font not found"));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-text", x.clone())?;
let y1 = to_i64("kbd/make-text", y.clone())?;
let x_w = add_to_i64("kbd/make-text", x, w)?;
let y_h = add_to_i64("kbd/make-text", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let halign = graphics::Alignment::Middle;
let valign = graphics::Alignment::Middle;
let direction = swash::shape::Direction::LeftToRight;
let script = swash::text::Script::Latin;
let color = image::ColorRgba([255, 0, 0, 255]);
// [xy, wh, text, font, font_size]
// println!("rendering text {text} of size {w}/{h} at {x}/{y} with font {font} {font_size}, color {color:?}, alignment {halign:?}/{valign:?}, script {script:?}, direction {direction:?}");
let text = graphics::WidgetEnum::Text(graphics::Text {
direction,
font_size: font_size as f32,
font_handle,
color,
halign,
valign,
script,
text: text.into(),
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
height: h as u32,
width: w as u32,
},
});
Value::from_serde(&text)
.map_err(|err| ErrorTrait::from_str("kbd/make-text", &err.to_string()))
}),
);
engine.register(":round-corners", Value::new_symbol(":round-corners"));
engine.register_func(
"kbd/make-rect",
Box::new(|_vm, args| {
let mut args = args.into_iter();
let Some(xy) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let Some(wh) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let mut round = 0.0f64;
while let Some(arg) = args.next() {
match arg {
Value::Symbol(x) if x == ":round-corners" => {
round = 1.0;
if let Some(arg) = args.next() {
let Value::Float(arg) = arg else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"float",
&format!("{arg:?}"),
));
};
round = arg;
}
}
x => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"make-rect keyword arguments",
&format!("{x:?}"),
))
}
}
}
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{wh:?}"),
));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-rect", x.clone())?;
let y1 = to_i64("kbd/make-rect", y.clone())?;
let x_w = add_to_i64("kbd/make-rect", x, w)?;
let y_h = add_to_i64("kbd/make-rect", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let color = image::ColorRgba([0, 0, 0, 255]);
// [xy, wh, text, font, font_size]
println!(
"rendering rect of size {w}/{h} at {x}/{y} with rounding {round}, color {color:?}"
);
let rect = graphics::WidgetEnum::Shape(graphics::SimpleShape {
col: color,
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
width: w as u32,
height: h as u32,
},
round_corners_ratio: round as f32,
});
Value::from_serde(&rect)
.map_err(|err| ErrorTrait::from_str("kbd/make-rect", &err.to_string()))
}),
);
engine.register_func(
"kbd/make-layout",
Box::new(|_vm, args| {
let Ok::<[Value<_>; 1], _>([items]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-layout"));
};
let Value::RevArray(items) = items else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list",
&format!("{items:?}"),
));
};
let mut layout = graphics::Layout::new();
for item in items.into_iter().rev() {
let Value::RevArray(mut item) = item else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list of lists",
&format!("list with {item:?}"),
));
};
let Some(Value::String(a)) = item.pop() else {
return Err(ErrorTrait::from_str(
"kbd/make-layout",
"each item's first element must be the item id",
));
};
let item = Value::RevArray(item)
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))?;
// println!("{item:#?}");
layout.add_child(item, &a, None);
}
Value::from_serde(&layout)
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/width",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 0], _>([]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/width"));
};
println!("getting keyboard width");
Ok(Value::Int(ctx1.borrow().width.into()))
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/load-font",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 1], _>([path]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/load-font"));
};
let Value::String(path) = path else {
return Err(ErrorTrait::invalid_argt(
"kbd/load-font",
"string",
&format!("{path:?}"),
));
};
println!("loading font {path:?}");
(*ctx1)
.borrow_mut()
.font_db
.load_font(path)
.map(|()| Value::Null)
.map_err(|err| ErrorTrait::from_str("kbd/load-font", &err.to_string()))
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/set-layout",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 1], _>([layout]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-layout"));
};
let layout: graphics::Layout = layout
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/set-layout", &err.to_string()))?;
(*ctx1).borrow_mut().layout = Some(layout);
Ok(Value::Null)
}),
);
engine.gc();
ScriptEnv(engine, ctx)
}
fn assert_type<T>(_: &T, _: &T) {}
fn main() {
env_logger::init();
let mut env = new_script_env();
// engine.gc();
let engine = script::create_engine();
let mut kbd: Keyboard<_> = Keyboard::new(engine);
let s = std::fs::read_to_string("tmp.scm").unwrap();
let mut kbd = Keyboard::new();
assert_type(&kbd, &(*env.1.borrow()));
env.eval(&mut kbd, &s).unwrap();
kbd.env = Some(env);
kbd.eval_t(&s).unwrap();
#[cfg(feature = "wayland")]
if std::env::var_os("WAYLAND_DISPLAY").is_some() {

View file

@ -42,6 +42,22 @@ impl<E: Engine> Clone for Value<E> {
}
impl<E: Engine> Value<E> {
pub fn convert<R: Engine>(self) -> Value<R>
where
R::Func: Func<BasicValue = <E::Func as Func>::BasicValue>,
{
match self {
Self::Null => Value::Null,
Self::Bool(x) => Value::Bool(x),
Self::Int(x) => Value::Int(x),
Self::Float(x) => Value::Float(x),
Self::String(x) => Value::String(x),
Self::Symbol(x) => Value::Symbol(x),
Self::RevArray(x) => Value::RevArray(x.into_iter().map(Self::convert).collect()),
Self::Pair(x) => Value::new_pair(x.0.convert(), x.1.convert()),
Self::Func(x) => Value::Func(x.convert::<R>()),
}
}
pub fn new_symbol(s: impl Into<Cow<'static, str>>) -> Self {
Self::Symbol(s.into())
}
@ -168,24 +184,16 @@ impl<E: Engine> Hash for Value<E> {
}
}
pub trait ToVal {
type Engine: Engine;
fn to_val2(&self, _engine: &Self::Engine) -> Value<Self::Engine> {
self.to_val()
}
fn to_val(&self) -> Value<Self::Engine> {
unimplemented!()
}
}
pub trait ErrorTrait {
fn from_str(name: &str, s: &str) -> Self;
fn invalid_argc(name: &str) -> Self;
fn invalid_argt(name: &str, exp: &str, found: &str) -> Self;
}
pub type Args<E> = Vec<Value<E>>;
pub trait Engine: Debug + Sized {
type Value: FromVal<Engine = Self> + ToVal<Engine = Self>;
type Value: ValTrait<Engine = Self>;
type Error: Debug + ErrorTrait;
type Func: Clone + Debug + Ord + Func<Engine = Self>;
fn eval_t(&mut self, text: &str) -> Result<Value<Self>, Self::Error>;
@ -194,32 +202,40 @@ pub trait Engine: Debug + Sized {
// this doesnt have to be box, but it being a box saves some binary size
fn new_func(
&mut self,
name: &'static str,
func: Box<
dyn 'static + Fn(&mut Self, Vec<Value<Self>>) -> Result<Value<Self>, Self::Error>,
>,
) -> Self::Func;
_name: &'static str,
_func: Box<dyn 'static + Fn(&mut Self, Args<Self>) -> Result<Value<Self>, Self::Error>>,
) -> Self::Func {
unimplemented!()
}
#[inline(always)]
fn register_func(
&mut self,
name: &'static str,
func: Box<
dyn 'static + Fn(&mut Self, Vec<Value<Self>>) -> Result<Value<Self>, Self::Error>,
>,
func: impl 'static + Fn(&mut Self, Args<Self>) -> Result<Value<Self>, Self::Error>,
) {
let func = self.new_func(name, func);
let func = self.new_func(name, Box::new(func));
self.register(name, Value::Func(func));
}
}
pub trait Func {
pub trait Func: Sized {
type Engine: Engine;
type BasicValue;
fn call(
&self,
engine: &mut Self::Engine,
args: Vec<Value<Self::Engine>>,
args: Args<Self::Engine>,
) -> Result<Value<Self::Engine>, <Self::Engine as Engine>::Error>;
fn add_ref(&mut self, engine: &mut Self::Engine);
fn unref(&mut self, engine: &mut Self::Engine);
fn from_basic_value(v: Self::BasicValue) -> Self;
fn into_basic_value(self) -> Self::BasicValue;
fn convert<R: Engine>(self) -> R::Func
where
R::Func: Func<BasicValue = Self::BasicValue>,
{
R::Func::from_basic_value(self.into_basic_value())
}
}
#[derive(Debug)]
@ -288,10 +304,17 @@ impl PartialOrd for MarwoodFunc {
impl Func for MarwoodFunc {
type Engine = marwood::vm::Vm;
type BasicValue = Self;
fn into_basic_value(self) -> Self::BasicValue {
self
}
fn from_basic_value(v: Self::BasicValue) -> Self {
v
}
fn call(
&self,
engine: &mut Self::Engine,
args: Vec<Value<Self::Engine>>,
args: Args<Self::Engine>,
) -> Result<Value<Self::Engine>, <Self::Engine as Engine>::Error> {
use marwood::vm::{lambda::Lambda, opcode::OpCode, vcell::VCell};
let (lambda, is_cont) = match self {
@ -437,15 +460,13 @@ impl Engine for marwood::vm::Vm {
fn register(&mut self, name: &str, val: Value<Self>) {
let symbol = self.heap.put(marwood::vm::vcell::VCell::symbol(name));
let slot = self.globenv.get_binding(symbol.as_ptr().unwrap());
let val = FromVal::from_val2(val, self);
let val = ValTrait::from_val2(val, self);
self.globenv.put_slot(slot, val);
}
fn new_func(
&mut self,
name: &'static str,
func: Box<
dyn 'static + Fn(&mut Self, Vec<Value<Self>>) -> Result<Value<Self>, Self::Error>,
>,
func: Box<dyn 'static + Fn(&mut Self, Args<Self>) -> Result<Value<Self>, Self::Error>>,
) -> Self::Func {
let func = marwood::vm::vcell::VCell::builtin(name, move |vm| {
// log::info!("calling {name}");
@ -457,7 +478,7 @@ impl Engine for marwood::vm::Vm {
args.reverse();
// log::info!("calling! {name}");
match func(vm, args) {
Ok(x) => Ok(FromVal::from_val2(x, vm)),
Ok(x) => Ok(ValTrait::from_val2(x, vm)),
Err(err) => Err(err),
}
// log::info!("exiting {name}");
@ -472,8 +493,14 @@ impl Engine for marwood::vm::Vm {
}
}
pub trait FromVal: Sized {
pub trait ValTrait: Sized {
type Engine: Engine;
fn to_val2(&self, _engine: &Self::Engine) -> Value<Self::Engine> {
self.to_val()
}
fn to_val(&self) -> Value<Self::Engine> {
unimplemented!()
}
fn from_val(_val: Value<Self::Engine>) -> Self {
unimplemented!()
}
@ -483,7 +510,7 @@ pub trait FromVal: Sized {
}
#[cfg(feature = "steel")]
impl FromVal for steel::SteelVal {
impl ValTrait for steel::SteelVal {
type Engine = steel::steel_vm::engine::Engine;
fn from_val(val: Value) -> Self {
match val {
@ -512,7 +539,7 @@ impl FromVal for steel::SteelVal {
}
}
impl FromVal for marwood::vm::vcell::VCell {
impl ValTrait for marwood::vm::vcell::VCell {
type Engine = marwood::vm::Vm;
fn from_val2(val: Value<Self::Engine>, engine: &mut Self::Engine) -> Self {
match val {
@ -568,10 +595,6 @@ impl FromVal for marwood::vm::vcell::VCell {
},
}
}
}
impl ToVal for marwood::vm::vcell::VCell {
type Engine = marwood::vm::Vm;
fn to_val2(&self, engine: &Self::Engine) -> Value<Self::Engine> {
match self {
Self::Bool(val) => Value::Bool(*val),

View file

@ -1,5 +1,6 @@
use std::{
collections::HashMap,
fmt::Debug,
hash::Hash,
io,
path::Path,
@ -22,11 +23,6 @@ impl CacheKey {
static KEY: AtomicU64 = AtomicU64::new(1);
Self(KEY.fetch_add(1, Ordering::Relaxed))
}
/// Returns the underlying value of the key.
pub fn value(self) -> u64 {
self.0
}
}
impl Default for CacheKey {
@ -69,6 +65,13 @@ pub struct FontDb {
fonts: HashMap<String, FontHandle>,
glyph_cache: HashMap<GlyphCacheKey, (Instant, Option<Image>)>,
}
impl Debug for FontDb {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FontDb")
.field("fonts", &self.fonts)
.finish_non_exhaustive()
}
}
impl FontDb {
pub fn load_font(&mut self, file: impl AsRef<Path>) -> io::Result<()> {