orbtk icon indicating copy to clipboard operation
orbtk copied to clipboard

Howto issue an event from inside another function

Open rzerres opened this issue 4 years ago • 4 comments

Abstract

You like to update a field or a widget inside a view, triggered by an action calculated in a external rust function. For now, imagnine the action is driven by a single thread (async is out of scope for now).

Problem description

You can register a ctx.window_sender(), that will enable you to call WindowRequest::Redraw. That will advise the receiver to redraw the widget. But in turn, to take effect in you app/window, this alway has to trigger an event. Imagine, you like to increase a progress_bar while reading in the records from an external file. You calculate the amount of records in the import function. Now you do loop and very 10% you'd like to visualize this progress via a progress_bar.

Examples and MockUps

Attached is some example code.

use std::collections::HashMap;
//use std::sync::mpsc;

use orbtk::prelude::*;
use orbtk::shell::WindowRequest;

static ID_REF_NUMBER: &'static str = "ID_REF_NUMBER";
static ID_PROGRESS_BAR: &'static str = "ID_PROGRESS_BAR";

enum Action {
    ParseRefNumber
}

#[derive(Default, AsAny)]
struct MainViewState {
    action: Option<Action>,
    progress_bar: Entity,
    text_box: Entity,
    progress_counter: f64
    //records: HashMap::<String, String>,
    //record_counter: u64
}

impl State for MainViewState {
    fn init(&mut self, _: &mut Registry, ctx: &mut Context) {
        self.text_box = ctx.entity_of_child(ID_REF_NUMBER).expect("Cannot get TextBox!");
        self.progress_bar = ctx.entity_of_child(ID_PROGRESS_BAR).expect("Cannot get progress bar !");
    }

    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        // if there is an action, process it
        if let Some(action) = &self.action {
            match action {
                Action::ParseRefNumber => {
                    let value_to_parse = ctx.get_widget(self.text_box).get::<String16>("text").clone();
                    self.parse_ref_number(value_to_parse, ctx);
                }
            }
            // Reset action
            self.action = None;
        }
    }
}

impl MainViewState {
    fn action(&mut self, action: Action) {
        self.action = Some(action);
    }

    fn parse_ref_number(&mut self, _value: String16, ctx: &mut Context) {
        self.import_file(ctx);
    }

    fn import_file(&mut self, ctx: &mut Context) {
        // code to import records from a file into a hashmap
        // will read in number_of_records = 100%
        // progress_counter should be incremented, if
        // read_in_loop will reach next 10% -> self.progress_counter += 0.1
        // now fire an event to update the widget

        // question: how to fire up the event inside import_records function,
        // without the need to mute "ID_REF_NUMBER" ?

        // given code just increments, if you change "ID_REF_NUMBER"
        self.progress_counter += 0.1;
        self.update_progress(ctx);
    }

    fn update_progress(&self, ctx: &mut Context) {
        // create a mpsc::Sender<WindowRequest> object
        let sender = ctx.window_sender();
        let mut pgbar = ctx.get_widget(self.progress_bar);
        pgbar.set::<f64>("val", self.progress_counter);
        // redraw screen if sender has changed
        // only way to trigger a redraw: create an event
        sender.send(WindowRequest::Redraw).unwrap()
    }
}

widget!(MainView<MainViewState>);

impl Template for MainView {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        self
        .margin(32.0)
        .child(
            Stack::new()
            .orientation("vertical")
            .h_align("center")
            .v_align("top")
            .spacing(8.0)
            .child(
                TextBox::new()
                .id(ID_REF_NUMBER)
                    .water_mark("Mutate the value and type <Return>")
                    .on_activate(move |states, _entity| {
                    // you have to fire a new event to be able to get in the update() with access to Context
                    states.get_mut::<MainViewState>(id).action(Action::ParseRefNumber);
                })
                .build(ctx)
            )
            .child(
                ProgressBar::new()
                .id(ID_PROGRESS_BAR)
                .build(ctx)
            )
            .build(ctx)
        )
    }
}

fn main() {
    Application::new()
        .window(|ctx| {
            Window::new()
                .title("increment_progress_bar skeleton")
                .position((100.0, 100.0))
                .size(420.0, 730.0)
                .resizeable(true)
                .child(MainView::new().build(ctx))
                .build(ctx)
        })
        .run();
}

rzerres avatar Aug 28 '20 09:08 rzerres

Now you could send messages between widgets. I made an example for you (you can adapt to your needs). I hope it helps.

use orbtk::prelude::*;

//|---------------------------------------------------------------------------|
//|------------------------------SENDER---------------------------------------|
//|---------------------------------------------------------------------------|

enum Action {
    UpdateProgress(f64)
}

#[derive(Default, AsAny)]
struct SenderState {
    actions: Vec<Action>,
    target: Entity
}

impl SenderState {
    fn send_message(&mut self) {
        self.actions.push(Action::UpdateProgress(0.1));
    }
}

impl State for SenderState {
    fn init(&mut self, _: &mut Registry, ctx: &mut Context) {
        self.target = Entity::from(ctx.widget().try_clone::<u32>("target")
            .expect("ERROR: SenderState::init(): target entity not found!"));
    }

    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        let actions: Vec<Action> = self.actions.drain(..).collect();

        for action in actions {
            match action {
                Action::UpdateProgress(amount) => {
                    ctx.send_message(Action::UpdateProgress(amount), self.target);
                    println!("Message sent!");
                }
            }
        }
    }
}

widget!(SenderWidget<SenderState> {
    // the Entity of the widget that will receive the messages
    target: u32
});

impl Template for SenderWidget {
    fn template(self, id: Entity, bc: &mut BuildContext) -> Self {
        self.name("SenderWidget")
            .child(
                Button::new()
                    .text("Click me to send a message!")
                    .v_align("center")
                    .h_align("center")
                    .on_click(move |states, _entity| {
                        states.get_mut::<SenderState>(id).send_message();
                        false
                    })
                    .build(bc)
            )
    }
}


//|---------------------------------------------------------------------------|
//|------------------------------RECEIVER-------------------------------------|
//|---------------------------------------------------------------------------|

#[derive(Default, AsAny)]
struct ReceiverState {
    progress_bar: Entity
}

impl State for ReceiverState {
    fn init(&mut self, _: &mut Registry, ctx: &mut Context) {
        self.progress_bar = ctx.entity_of_child("progress_bar").expect("Cannot find ProgressBar!");
    }

    fn messages(&mut self, mut messages: MessageReader, _registry: &mut Registry, ctx: &mut Context) {
        for action in messages.read::<Action>() {
            match action {
                Action::UpdateProgress(amount) => {
                    println!("Message received");
                    let mut progress_bar = ctx.get_widget(self.progress_bar);
                    let current_progress = progress_bar.clone::<f64>("val");
                    progress_bar.set::<f64>("val", current_progress + amount);
                }
            }
        }
    }
}

widget!(ReceiverWidget<ReceiverState>);

impl Template for ReceiverWidget {
    fn template(self, _id: Entity, bc: &mut BuildContext) -> Self {
        self.name("ReceiverWidget")
            .child(
                ProgressBar::new()
                    .id("progress_bar")
                    .build(bc)
            )
    }
}

pub fn main() {
    Application::new()
        .window(|ctx| {
            let receiver = ReceiverWidget::new().build(ctx);

            let sender = SenderWidget::new()
                // the entity of the target (receiver)
                .target(receiver.0)
                .build(ctx);

            Window::new()
                .title("Messages example")
                .position((100.0, 100.0))
                .resizeable(true)
                .size(450.0, 500.0)
                .child(
                    Stack::new()
                        .orientation("vertical")
                        .child(sender)
                        .child(receiver)
                        .build(ctx)
                )
                .build(ctx)
        })
        .run();
}

kivimango avatar Oct 22 '20 17:10 kivimango

@FloVanGH @kivimango This should be taken as a reference - for the showcase and for the book.

I would love to transfer kivimangos reference implementation to the original documented use case.

  • because i do need it in my own app
  • since it would improve the showcase with a real world need (reading io and realtime feedback to the user)

rzerres avatar Oct 23 '20 08:10 rzerres

Looks like both examples start from some event, like a click. Is there an "onload" event, so that I can kick off an animation loop with it?

xixixao avatar Dec 02 '20 07:12 xixixao

Im not really into the event part of the codebase, but you could start the animation in one of the widget's State init() method.

kivimango avatar Dec 05 '20 10:12 kivimango