summaryrefslogtreecommitdiffstats
path: root/native/src/program
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--native/src/program.rs39
-rw-r--r--native/src/program/state.rs154
2 files changed, 193 insertions, 0 deletions
diff --git a/native/src/program.rs b/native/src/program.rs
new file mode 100644
index 00000000..2652dee9
--- /dev/null
+++ b/native/src/program.rs
@@ -0,0 +1,39 @@
+//! Build interactive programs using The Elm Architecture.
+use crate::{Command, Element, Renderer};
+
+mod state;
+
+pub use state::State;
+
+/// An interactive, native cross-platform program.
+pub trait Program: Sized {
+ /// The graphics backend to use to draw the [`Program`].
+ ///
+ /// [`Program`]: trait.Program.html
+ type Renderer: Renderer;
+
+ /// The type of __messages__ your [`Program`] will produce.
+ ///
+ /// [`Application`]: trait.Program.html
+ type Message: std::fmt::Debug + Send;
+
+ /// Handles a __message__ and updates the state of the [`Program`].
+ ///
+ /// This is where you define your __update logic__. All the __messages__,
+ /// produced by either user interactions or commands, will be handled by
+ /// this method.
+ ///
+ /// Any [`Command`] returned will be executed immediately in the
+ /// background by shells.
+ ///
+ /// [`Program`]: trait.Application.html
+ /// [`Command`]: struct.Command.html
+ fn update(&mut self, message: Self::Message) -> Command<Self::Message>;
+
+ /// Returns the widgets to display in the [`Program`].
+ ///
+ /// These widgets can produce __messages__ based on user interaction.
+ ///
+ /// [`Program`]: trait.Application.html
+ fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>;
+}
diff --git a/native/src/program/state.rs b/native/src/program/state.rs
new file mode 100644
index 00000000..bcb7212d
--- /dev/null
+++ b/native/src/program/state.rs
@@ -0,0 +1,154 @@
+use crate::{
+ Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size,
+ UserInterface,
+};
+
+#[allow(missing_debug_implementations)]
+pub struct State<P>
+where
+ P: Program + 'static,
+{
+ program: P,
+ cache: Option<Cache>,
+ primitive: <P::Renderer as Renderer>::Output,
+ queued_events: Vec<Event>,
+ queued_messages: Vec<P::Message>,
+}
+
+impl<P> State<P>
+where
+ P: Program + 'static,
+{
+ pub fn new(
+ mut program: P,
+ bounds: Size,
+ renderer: &mut P::Renderer,
+ debug: &mut Debug,
+ ) -> Self {
+ let user_interface = build_user_interface(
+ &mut program,
+ Cache::default(),
+ renderer,
+ bounds,
+ debug,
+ );
+
+ debug.draw_started();
+ let primitive = user_interface.draw(renderer);
+ debug.draw_finished();
+
+ let cache = Some(user_interface.into_cache());
+
+ State {
+ program,
+ cache,
+ primitive,
+ queued_events: Vec::new(),
+ queued_messages: Vec::new(),
+ }
+ }
+
+ pub fn program(&self) -> &P {
+ &self.program
+ }
+
+ pub fn primitive(&self) -> &<P::Renderer as Renderer>::Output {
+ &self.primitive
+ }
+
+ pub fn queue_event(&mut self, event: Event) {
+ self.queued_events.push(event);
+ }
+
+ pub fn queue_message(&mut self, message: P::Message) {
+ self.queued_messages.push(message);
+ }
+
+ pub fn update(
+ &mut self,
+ clipboard: Option<&dyn Clipboard>,
+ bounds: Size,
+ renderer: &mut P::Renderer,
+ debug: &mut Debug,
+ ) -> Option<Command<P::Message>> {
+ if self.queued_events.is_empty() && self.queued_messages.is_empty() {
+ return None;
+ }
+
+ let mut user_interface = build_user_interface(
+ &mut self.program,
+ self.cache.take().unwrap(),
+ renderer,
+ bounds,
+ debug,
+ );
+
+ debug.event_processing_started();
+ let mut messages = user_interface.update(
+ self.queued_events.drain(..),
+ clipboard,
+ renderer,
+ );
+ messages.extend(self.queued_messages.drain(..));
+ debug.event_processing_finished();
+
+ if messages.is_empty() {
+ debug.draw_started();
+ self.primitive = user_interface.draw(renderer);
+ debug.draw_finished();
+
+ self.cache = Some(user_interface.into_cache());
+
+ None
+ } else {
+ // When there are messages, we are forced to rebuild twice
+ // for now :^)
+ let temp_cache = user_interface.into_cache();
+
+ let commands =
+ Command::batch(messages.into_iter().map(|message| {
+ debug.log_message(&message);
+
+ debug.update_started();
+ let command = self.program.update(message);
+ debug.update_finished();
+
+ command
+ }));
+
+ let user_interface = build_user_interface(
+ &mut self.program,
+ temp_cache,
+ renderer,
+ bounds,
+ debug,
+ );
+
+ debug.draw_started();
+ self.primitive = user_interface.draw(renderer);
+ debug.draw_finished();
+
+ self.cache = Some(user_interface.into_cache());
+
+ Some(commands)
+ }
+ }
+}
+
+fn build_user_interface<'a, P: Program>(
+ program: &'a mut P,
+ cache: Cache,
+ renderer: &mut P::Renderer,
+ size: Size,
+ debug: &mut Debug,
+) -> UserInterface<'a, P::Message, P::Renderer> {
+ debug.view_started();
+ let view = program.view();
+ debug.view_finished();
+
+ debug.layout_started();
+ let user_interface = UserInterface::build(view, size, cache, renderer);
+ debug.layout_finished();
+
+ user_interface
+}