All Projects → jazzfool → Reclutch

jazzfool / Reclutch

Licence: other
Rust UI Core

Programming Languages

rust
11053 projects

Labels

Projects that are alternatives of or similar to Reclutch

Sequelize Ui
Browser-based GUI for previewing and generating Sequelize project files.
Stars: ✭ 142 (-7.19%)
Mutual labels:  gui
Totalcross
TotalCross is a Software Development Kit that helps cross platform application development. Currently supported platforms are: Windows, Wince, Android, iOS, Linux and Linux ARM for embedded systems.
Stars: ✭ 147 (-3.92%)
Mutual labels:  gui
Win Vind
Simple Vim Key Binder for Windows. You can operate Windows with keybindings like Vim.
Stars: ✭ 151 (-1.31%)
Mutual labels:  gui
Rapidgui
Unity OnGUI(IMGUI) extensions for Rapid prototyping/development
Stars: ✭ 144 (-5.88%)
Mutual labels:  gui
Omnigui
A cross-platform GUI framework from scratch just to learn
Stars: ✭ 147 (-3.92%)
Mutual labels:  gui
Resumeparser
A simple resume parser used for extracting information from resumes
Stars: ✭ 150 (-1.96%)
Mutual labels:  gui
Java Rpg Maker Mv Decrypter
You can decrypt whole RPG-Maker MV Directories with this Program, it also has a GUI.
Stars: ✭ 142 (-7.19%)
Mutual labels:  gui
Akhq
Kafka GUI for Apache Kafka to manage topics, topics data, consumers group, schema registry, connect and more...
Stars: ✭ 2,195 (+1334.64%)
Mutual labels:  gui
Umbrella
"A collection of functional programming libraries that can be composed together. Unlike a framework, thi.ng is a suite of instruments and you (the user) must be the composer of. Geared towards versatility, not any specific type of music." — @loganpowell via Twitter
Stars: ✭ 2,186 (+1328.76%)
Mutual labels:  gui
Libs Gui
The GNUstep gui library is a library of graphical user interface classes written completely in the Objective-C language; the classes are based upon Apple's Cocoa framework (which came from the OpenStep specification). *** Larger patches require copyright assignment to FSF. please file bugs here. ***
Stars: ✭ 148 (-3.27%)
Mutual labels:  gui
Scap Workbench
SCAP Scanner And Tailoring Graphical User Interface
Stars: ✭ 145 (-5.23%)
Mutual labels:  gui
Qamel
Simple QML binding for Go
Stars: ✭ 147 (-3.92%)
Mutual labels:  gui
Zookeeper Visualizer
zookeeper的可视化管理工具
Stars: ✭ 150 (-1.96%)
Mutual labels:  gui
Bimpy
imgui for python
Stars: ✭ 144 (-5.88%)
Mutual labels:  gui
Tether
🖥 A barebones Electron-ish library for Rust.
Stars: ✭ 151 (-1.31%)
Mutual labels:  gui
Docker X11 Bridge
Simple Xpra X11 bridge to enable GUI with any docker image
Stars: ✭ 143 (-6.54%)
Mutual labels:  gui
Carry
ClojureScript application framework.
Stars: ✭ 149 (-2.61%)
Mutual labels:  gui
Clamtk
An easy to use, light-weight, on-demand virus scanner for Linux systems
Stars: ✭ 151 (-1.31%)
Mutual labels:  gui
Ropa
GUI tool to create ROP chains using the ropper API
Stars: ✭ 151 (-1.31%)
Mutual labels:  gui
Json Splora
GUI for editing, visualizing, and manipulating JSON data
Stars: ✭ 1,818 (+1088.24%)
Mutual labels:  gui

Reclutch

Build Status

A strong foundation for building predictable and straight-forward Rust UI toolkits. Reclutch is:

  • Bare: Very little UI code is included. In practice it's a utility library which makes very little assumptions about the toolkit or UI.
  • Platform-agnostic: Although a default display object is provided, the type of display object is generic, meaning you can build for platforms other than desktop. For example you can create web applications simply by using DOM nodes as display objects while still being efficient, given the retained-mode design.
  • Reusable: Provided structures such as unbound queue handlers allow for the reuse of common logical components across widgets.

Overview

Reclutch implements the well-known retained-mode widget ownership design within safe Rust, following along the footsteps of popular desktop frameworks. To implement this behavior, three core ideas are implemented:

  • A widget ownership model with no middleman, allowing widgets to mutate children at any time, but also collect children as a whole to make traversing the widget tree a trivial task.
  • A robust event queue system with support for futures, crossbeam and winit event loop integration, plus a multitude of queue utilities and queue variations for support in any environment.
  • An event queue abstraction to facilitate just-in-time event coordination between widgets, filling any pitfalls that may arise when using event queues. Beyond this, it also moves the code to handle queues to the constructor, presenting an opportunity to modularize and reuse logic across widgets.

Note for MacOS

There appears to be a bug with shared OpenGL textures on MacOS. As a result, the opengl example won't work correctly. For applications that require rendering from multiple contexts into a single texture, consider using Vulkan or similar.

Also see:

Example

All rendering details have been excluded for simplicity.

#[derive(WidgetChildren)]
struct Button {
    pub button_press: RcEventQueue<()>,
    graph: VerbGraph<Button, ()>,
}

impl Button {
    pub fn new(global: &mut RcEventQueue<WindowEvent>) -> Self {
        Button {
            button_press: RcEventQueue::new(),
            global_listener: VerbGraph::new().add(
                "global",
                QueueHandler::new(global).on("click", |button, _aux, _event: WindowEvent| {
                    button.button_press.emit_owned(());
                }),
            ),
        }
    }
}

impl Widget for Button {
    type UpdateAux = ();
    type GraphicalAux = ();
    type DisplayObject = DisplayCommand;

    fn bounds(&self) -> Rect { /* --snip-- */ }

    fn update(&mut self, aux: &mut ()) {
        // Note: this helper function requires that `HasVerbGraph` be implemented on `Self`.
        reclutch_verbgraph::update_all(self, aux);
        // The equivalent version which doesn't require `HasVerbGraph` is;
        let mut graph = self.graph.take().unwrap();
        graph.update_all(self, aux);
        self.graph = Some(graph);
    }

    fn draw(&mut self, display: &mut dyn GraphicsDisplay, _aux: &mut ()) { /* --snip-- */ }
}

The classic counter example can be found in examples/overview.


Children

Children are stored manually by the implementing widget type.

#[derive(WidgetChildren)]
struct ExampleWidget {
    #[widget_child]
    child: AnotherWidget,
    #[vec_widget_child]
    children: Vec<AnotherWidget>,
}

Which expands to exactly...

impl reclutch::widget::WidgetChildren for ExampleWidget {
    fn children(
        &self,
    ) -> Vec<
        &dyn reclutch::widget::WidgetChildren<
            UpdateAux = Self::UpdateAux,
            GraphicalAux = Self::GraphicalAux,
            DisplayObject = Self::DisplayObject,
        >,
    > {
        let mut children = Vec::with_capacity(1 + self.children.len());
        children.push(&self.child as _);
        for child in &self.children {
            children.push(child as _);
        }
        children
    }

    fn children_mut(
        &mut self,
    ) -> Vec<
        &mut dyn reclutch::widget::WidgetChildren<
            UpdateAux = Self::UpdateAux,
            GraphicalAux = Self::GraphicalAux,
            DisplayObject = Self::DisplayObject,
        >,
    > {
        let mut children = Vec::with_capacity(1 + self.children.len());
        children.push(&mut self.child as _);
        for child in &mut self.children {
            children.push(child as _);
        }
        children
    }
}

(Note: you can switch out the reclutch::widget::WidgetChildrens above with your own trait using #[widget_children_trait(...)])

Then all the other functions (draw, update, maybe even bounds for parent clipping) are propagated manually (or your API can have a function which automatically and recursively invokes for both parent and child);

fn draw(&mut self, display: &mut dyn GraphicsDisplay) {
    // do our own rendering here...

    // ...then propagate to children
    for child in self.children_mut() {
        child.draw(display);
    }
}

Note: WidgetChildren requires that Widget is implemented.

The derive functionality is a feature, enabled by default.

Rendering

Rendering is done through "command groups". It's designed in a way that both a retained-mode renderer (e.g. WebRender) and an immediate-mode renderer (Direct2D, Skia, Cairo) can be implemented. The API also supports Z-Order.

struct VisualWidget {
    command_group: CommandGroup,
}

impl Widget for VisualWidget {
    // --snip--

    fn update(&mut self, _aux: &mut ()) {
        if self.changed {
            // This simply sets an internal boolean to "true", so don't be afraid to call it multiple times during updating.
            self.command_group.repaint();
        }
    }

    // Draws a nice red rectangle.
    fn draw(&mut self, display: &mut dyn GraphicsDisplay, _aux: &mut ()) {
        let mut builder = DisplayListBuilder::new();
        builder.push_rectangle(
            Rect::new(Point::new(10.0, 10.0), Size::new(30.0, 50.0)),
            GraphicsDisplayPaint::Fill(Color::new(1.0, 0.0, 0.0, 1.0).into()),
            None);

        // Only pushes/modifies the command group if a repaint is needed.
        self.command_group.push(display, &builder.build(), Default::default(), None, true);

        draw_children();
    }

    // --snip--
}

Updating

The update method on widgets is an opportunity for widgets to update layout, animations, etc. and more importantly handle events that have been emitted since the last update.

Widgets have an associated type; UpdateAux which allows for a global object to be passed around during updating. This is useful for things like updating a layout.

Here's a simple example;

type UpdateAux = Globals;

fn update(&mut self, aux: &mut Globals) {
    if aux.layout.node_is_dirty(self.layout_node) {
        self.bounds = aux.layout.get_node(self.layout_node);
        self.command_group.repaint();
    }

    self.update_animations(aux.delta_time());

    // propagation is done manually
    for child in self.children_mut() {
        child.update(aux);
    }

    // If your UI doesn't update constantly, then you must check child events *after* propagation,
    // but if it does update constantly, then it's more of a micro-optimization, since any missed events
    // will come back around next update.
    //
    // This kind of consideration can be avoided by using the more "modern" updating API; `verbgraph`,
    // which is discussed in the "Updating correctly" section.
    for press_event in self.button_press_listener.peek() {
        self.on_button_press(press_event);
    }
}

Updating correctly

The above code is fine, but for more a complex UI then there is the possibility of events being processed out-of-order. To fix this, Reclutch has the verbgraph module; a facility to jump between widgets and into their specific queue handlers. In essence, it breaks the linear execution of update procedures so that dependent events can be handled even if the primary update function has already be executed.

This is best shown through example;

fn new() -> Self {
    let graph = verbgraph! {
        Self as obj,
        Aux as aux,

        // the string "count_up" is the tag used to identify procedures.
        // they can also overlap.
        "count_up" => event in &count_up.event => {
            click => {
                // here we mutate a variable that `obj.template_label` implicitly/indirectly depends on.
                obj.count += 1;
                // Here template_label is assumed to be a label whose text uses a template engine
                // that needs to be explicitly rendered.
                obj.template_label.values[0] = obj.count.to_string();
                // If we don't call this then `obj.dynamic_label` doesn't
                // get a chance to respond to our changes in this update pass.
                // This doesn't invoke the entire update cycle for `template_label`, only the specific part we care about; `"update_template"`.
                reclutch_verbgraph::require_update(&mut obj.template_label, aux, "update_template");
                // "update_template" refers to the tag.
            }
        }
    };
    // ...
}

fn update(&mut self, aux: &mut Aux) {
    for child in self.children_mut() {
        child.update(aux);
    }

    reclutch_verbgraph::update_all(self, aux);
}

In the verbgraph module is also the Event trait, which is required to support the syntax seen in verbgraph!.

#[derive(Event, Clone)]
enum AnEvent {
    #[event_key(pop)]
    Pop,
    #[event_key(squeeze)]
    Squeeze(f32),
    #[event_key(smash)]
    Smash {
        force: f64,
        hulk: bool,
    },
}

Generates exactly;

impl reclutch::verbgraph::Event for AnEvent {
    fn get_key(&self) -> &'static str {
        match self {
            AnEvent::Pop => "pop",
            AnEvent::Squeeze(..) => "squeeze",
            AnEvent::Smash{..} => "smash",
        }
    }
}

impl AnEvent {
    pub fn unwrap_as_pop(self) -> Option<()> {
        if let AnEvent::Pop = self {
            Some(())
        } else {
            None
        }
    }

    pub fn unwrap_as_squeeze(self) -> Option<(f32)> {
        if let AnEvent::Squeeze(x0) = self {
            Some((x0))
        } else {
            None
        }
    }

    pub fn unwrap_as_smash(self) -> Option<(f64, bool)> {
        if let AnEvent::Smash{force, hulk} = self {
            Some((force, hulk))
        } else {
            None
        }
    }
}

get_key is used to find the correct closure to execute given an event and unwrap_as_ is used to extract the inner information from within the given closure (because once get_key is matched then we can be certain it is of a certain variant).

License

Reclutch is licensed under either

at your choosing.

This license also applies to all "sub-projects" (event, derive and verbgraph).

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].