Code Monkey home page Code Monkey logo

egui_hooks's Introduction

egui_hooks

React Hooks like API for enhancing the ergonomics of egui::Memory

Overview

This crate provids React Hooks like API for egui.

Though this started as a toy project, eventually I found that it's definitely useful and that could be a core building block for widget development, and also for application development.

Features

  • No resource leak: Opposite to using egui::Memory directly, the states are automatically freed from the HashMap when the widget will be no longer displayed. This is based on TwoFrameMap (2f kv) defined in this crate.
  • No locking nor callback: You can manage states without ui.data(|| { ... }). This is because hooks encapsulate the underlying RwLock operation.
  • Dependency tracking: Hooks has dependencies like use_state(|| user_id.clone(), user_id) or use_effect(|| log(input), input), so you can precisely track the dependencies without manually writing if statements on state change.
  • Composable: Hooks are composable, you can call existing hooks in your custom hooks.
  • Familiar API: Hooks are designed to be similar to React Hooks API, so you can easily learn how to use them. Managing UI states in UI side is the key in recent UI development scene, but built-in egui::Memory is relatively low-level API and not friendly for applcation development, and egui_hooks provides a higher level API but with more precise control.

How it works

If you use use_state(|| 0usize, dep).into_var() in a widget, the following things happen:

  1. On the first call of use_state, it creates a Arc<ArcSwap<usize>> in the egui::Memory with the default value.
  2. If the dep is changed since the last frame, it stores the default value to the existing ArcSwap.
  3. Returns a Var<usize> to the caller.
  4. Caller can Deref or DerefMut the Var in their widget code.
  5. When the Var is dropped, it stores the updated value to the ArcSwap.
  6. Wenn the widget is no longer displayed, the ArcSwap is removed from the egui::Memory.

This is the typical lifecycle of a hook in egui_hooks.

Also, there is a persistent version of use_state called use_persisted_state. It does the similar thing, but it stores the copy of the state to the egui::Memory with persisted methods. The persisted state is freed when the widget is no longer displayed as like the not-persisted one.

Intended use cases

  • use_state for states in a specific widget (e.g. animation state, scroll position)
  • use_state with into_var() to feed a variable in-place to Window::open or TextEdit::singleline
  • use_memo, use_cache for caching expensive calculation
  • use_effect, use_future for side effects (e.g. logging, network request)
  • use_global for global settings (e.g. theme, locale)
  • use_kv for sharing states between widgets (e.g. getting a position of a specific widget)
  • use_ephemeral_kv for storing events in the current frame (e.g. providing custom response on a custom widget)
  • use_previous_measurement for using the previous frame result for layouting
  • use_measurement for calculating and memoizing the size of a widget for layouting

Status

  • use_memo
  • use_effect
  • use_effect_with_cleanup
  • use_state, use_persisted_state
  • state.into_var() to use state as a variable
  • use_kv, use_persisted_kv
  • use_2f_kv, use_persisted_2f_kv
  • use_ephemeral_kv
  • use_global, use_persisted_global, and use_ephemeral_global
  • use_cache (a thin wrapper of caches in egui::Memory)
  • use_previous_measurement
  • use_measurement (calculate the size of the widget without fear of the 2^N problem.
  • use_future (needs tokio feature)
  • use_throttle and use_debounce
  • use_drag_origin
  • use_two_path (it's joke, but really want to implement this)

Usage

  1. use_state
// You can reset the initial state by changing the dependency part.
let count = ui.use_state(|| 0usize, ());
ui.label(format!("Count: {}", count));
if ui.button("Increment").clicked() {
    count.set_next(*count + 1);
}
  1. use_memo
let count = ui.use_state(|| 0usize, ());
let memo = ui.use_memo(
    || {
        println!("Calculating memoized value");
        count.pow(2)
    },
    count.clone(),
);
ui.label(format!("Memo: {}", memo));
if ui.button("Increment").clicked() {
    count.set_next(*count + 1);
}

Custom Hooks

You can create your own hooks by the two ways.

  1. Creating a function for a hook

This is the simplest and recommended way to create a custom hook.

fn use_search(ui: &mut Ui, backend: Backend) -> Option<SearchResults> {
    let text = ui.use_state(|| String::default(), ()).into_var();
    ui.text_edit_singleline(&mut *name);
    ui.use_future(async {
        backend.search(name.get()).await
    }, name.state())
}
  1. Implement Hook trait

All built-in hooks are implemented in this way. This allow you to create a hook with full control, but it is a bit verbose.

impl<D> Hook<D> for MyHook {
    type Backend = ()
    type Output = usize;

    fn init(
        &mut self,
        _index: usize,
        _deps: &D,
        _backend: Option<Self::Backend>,
        _ui: &mut egui::Ui,
    ) -> Self::Backend {
    }

    fn hook(self, backend: &mut Self::Backend, ui: &mut egui::Ui) -> Self::Output {
        let count = ui.use_state(0usize, ());
        ui.label(format!("Count: {}", count));
        if ui.button("Increment").clicked() {
            count.set_next(*count + 1);
        }
        count
    }
}

egui_hooks's People

Contributors

aspcartman avatar ryo33 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.