gyscos / cursive

A Text User Interface library for the Rust programming language
MIT License
4.26k stars 243 forks source link

[FEATURE] Fallback Event Handler for unhandled events #576

Open OvermindDL1 opened 3 years ago

OvermindDL1 commented 3 years ago

Is your feature request related to a problem? Please describe.

I'm needing to pass the character and key codes up to another processing layer if nothing in the cursive stack handled them, however it seems the primary way to do that is via add_global_callback and set_on_post_event and such calls on the CursiveRunner<CursiveRunnable> and such objects. The add_global_callback just delegates to set_on_post_event after changing the Event type to an EventTrigger type. If I implement EventTrigger directly and call set_on_post_event with it then I'm able to get the callback called for every event, however I don't know what event actually caused it as the callback only takes a &mut Cursive. Looking inside set_on_post_event shows that it just calls self.root.set_on_event(trigger, crate::immut1!(cb)); in the Cursive object, where self.root is the RootNode (an views::OnEventView type), and the OnEventView::set_on_event call just ends up delegating to OnEventView::set_on_event_inner, of which it like all the above are pub. The OnEventView::set_on_event_inner call is different than the prior in that it takes a callback that takes both a &mut T, &Event where T is the views::ScreensView<views::StackView> that is passed in to the OnEventView::init.

In short it looks like all the information is there to be able to call a callback that takes a (&mut Cursive, &Event) (and perhaps the T view as well), but not finding any way to do so on the root node.

Describe the solution you'd like

Optimally a way to register a fallback event handler that gets passed events that were not handled from within the Cursive view stack that passes at least the &mut Cursive and &Event that caused it (and perhaps the root view but that seems needless for me though could be useful in general) so we can handle things like a Char(_) for any character so we can handle unhandled events for other purposes.

Describe alternatives you've considered

I've tried looking for callbacks that can register both a &mut Cursive and an &Event on the root node. Tried looking to see if anything else is called if the root node doesn't handle an event, etc... etc... Nothing found. Tried looking for ways to acquire access to the root view via alternative methods, other event hooks that I could manually trigger, etc... Having difficulty finding any way through.

Additional context

In this case I'm wanting to pass unhandled character's, modifiers of characters, and all other possible keypresses up to another library after translating the types so it can handle the inputs for things that Cursive itself did not explicitly handle.

gyscos commented 3 years ago

Hi, and thanks for the report!

Indeed, it looks like more features from the OnEventView should be exposed by Cursive.

gyscos commented 3 years ago

Looking more into it, set_on_event_inner may fit the bill: the idea is to split the code between a part that only needs the event (the callback itself), and a part that access &mut Cursive.

use cursive::event::{Callback, Event, EventResult};

fn main() {                                                                 
    let mut siv = cursive::Cursive::new();                                                                       

    siv.set_on_event_inner( 
        // Here we just fetch all events, but we could filter out just some of them. 
        |event: &Event| matches!(event, Event::Char(_)), 
        |event: &Event| {                                                             
            // Here we just clone the event, but you could instead filter/post-process it.                       
            let event = event.clone();                                                                          
            Some(EventResult::Consumed(Some(Callback::from_fn_once(                   
                move |s| {                                                            
                    // Here you just forward the event back to the root, but you could do something else instead.
                    s.on_event(event.clone());                                        
                },                                                                    
            ))))                                                                      
        },                                                                            
    );                                                                                                           
}