///|
struct Update[Model, Message]((Model, Message, (Message) -> Unit) -> Model)
///|
pub fn[Model, Message] update(
f : (Model, Message) -> Model,
) -> Update[Model, Message] {
Update((model, message, _) => f(model, message))
}
///|
pub fn[Model, Message, Effect] effect(
update : (Model, Message) -> (Model, Effect),
handle : (Effect, (Message) -> Unit) -> Unit,
) -> Update[Model, Message] {
Update((model, message, dispatch) => {
let (model, effect) = update(model, message)
handle(effect, dispatch)
model
})
}
///|
priv struct State[Model, Message] {
app : @gtk.Application
mut model : Model
render : (Model) -> VWidget[Message]
update : (Model, Message, (Message) -> Unit) -> Model
subscribe : @sub.Subscribe[Model, Message]?
mut widget : IWidget?
mut dirty : Bool
mut pending : Bool
mut dispatching : Bool
queue : @deque.Deque[Message]
}
///|
fn[Model, Message] State::apply(
self : State[Model, Message],
message : Message,
dispatch : (Message) -> Unit,
) -> Unit {
self.model = (self.update)(self.model, message, dispatch)
if self.subscribe is Some(subscribe) {
subscribe(self.model, dispatch)
}
}
///|
fn[Model, Message] State::dispatch(
self : State[Model, Message],
message : Message,
) -> Unit {
if self.dispatching {
self.queue.push_back(message)
return
}
self.dispatching = true
let dispatch = msg => self.dispatch(msg)
self.apply(message, dispatch)
while self.queue.pop_front() is Some(msg) {
self.apply(msg, dispatch)
}
self.dispatching = false
self.dirty = true
self.schedule()
}
///|
fn[Model, Message] State::render(self : State[Model, Message]) -> Unit {
if !self.dirty {
return
}
self.dirty = false
let dispatch = message => self.dispatch(message)
let new_view = (self.render)(self.model)
if self.widget is Some(old_widget) {
self.widget = Some(
reconcile_widget(self.app, dispatch, old_widget, new_view),
)
} else {
self.widget = Some(new_view.create(self.app, dispatch))
}
}
///|
fn[Model, Message] State::schedule(self : State[Model, Message]) -> Unit {
if self.pending {
return
}
self.pending = true
@glib.idle_add(() => {
self.pending = false
self.render()
false
})
|> ignore()
}
///|
fn[Model, Message] start_app(
app : @gtk.Application,
model~ : Model,
init? : Message,
update~ : (Model, Message, (Message) -> Unit) -> Model,
subscribe? : @sub.Subscribe[Model, Message],
render~ : (Model) -> VWidget[Message],
) -> Unit {
let state : State[Model, Message] = {
app,
render,
update,
subscribe,
model,
widget: None,
dirty: true,
pending: false,
dispatching: false,
queue: @deque.Deque([]),
}
app.connect_activate(_ => {
let dispatch = message => state.dispatch(message)
if init is Some(message) {
dispatch(message)
}
if subscribe is Some(subscribe) {
subscribe(state.model, dispatch)
}
state.render()
})
|> ignore()
@sys.exit(app.run())
}
///|
pub fn[Model, Message] app(
id? : String = "com.giraffe.app",
model~ : Model,
init? : Message,
update~ : Update[Model, Message],
render~ : (Model) -> VWidget[Message],
subscribe? : @sub.Subscribe[Model, Message],
) -> Unit {
let app = @gtk.Application::new(Some(id), @gio.ApplicationFlags([]))
start_app(app, model~, init?, update=update.0, render~, subscribe?)
}
///|
pub fn[Model, Message] adw_app(
id? : String = "com.giraffe.app",
model~ : Model,
init? : Message,
update~ : Update[Model, Message],
render~ : (Model) -> VWidget[Message],
subscribe? : @sub.Subscribe[Model, Message],
) -> Unit {
let adw_application = @adw.Application::new(
Some(id),
@gio.ApplicationFlags([]),
)
start_app(
adw_application.as_gtk_application(),
model~,
init?,
update=update.0,
render~,
subscribe?,
)
}