[][src]Crate pugl_ui

pugl is a minimal portable API for embeddable GUIs. This crate aims to provide a stub for GUI-toolkits using pugl

pugl-ui (this crate) features

  • Widget layouting
  • Event propagation
  • Interaction with the windowing system via pugl-sys and pugl.

It does not feature actual widgets, though.

API principles

pugl-ui's API differs from classical object oriented approaches of GUI programming. This is due to Rust's safe ownership concepts which disallows shared mutable references to objects.

For example if a click on a button was to change the state of something in the app, usually the button would retain a reference or a callback to this "something". When the button is clicked it can use this reference to perform the state change.

In Rust that's not possible as the consequence of the button retaining a mutable reference to the state would be that no other reference – not even a readable one – could coexist in the application.

The players: Widgets, the UI, the Application

pugl-ui has in principle three players.

  • The widgets: they receive event notifications and can then change their internal state. Widgets must implement Widget

  • The UI: an instance of UI

    The UI is the interface between the application, the windowing system and the widgets. It receives event notifications from the windowing system and passes them to the widgets. Then the application can borrow references to individual widgets to check if the application's state needs to be changed.

  • The application holds a reference to the UI and implements the event loop. There is no trait nor struct for it in pugl-ui. Typically its a function that initializes the UI and then has an event loop that asks the UI to propagate events from the windowing system by calling UI::next_event() and then checks the widgets if any application state change is required, for example when a button has been clicked. So it is the application that holds all the application logic. The application can also borrow mutable references to widgets, for example to change their state.

Widget handles

The application does not retain references to the widget. It is the UI that has them. The application retains only WidgetHandle objects. The WidgetHandle object are created by UI::new_widget() and can later be accessed by UI::widget().

Example

use pugl_sys::*;
use pugl_ui::ui::*;
use pugl_ui::layout::stacklayout::*;
use pugl_ui::widget::*;
use pugl_ui::*;
use cairo;

// A simple root widget, that does only draw a gray background.
#[derive(Default)]
struct RootWidget {
    stub: WidgetStub,
}

impl Widget for RootWidget {
    widget_stub!();
    fn exposed (&mut self, _expose: &ExposeArea, cr: &cairo::Context) {
        cr.set_source_rgb(0.2, 0.2, 0.2);
        let size = self.size();
        cr.rectangle(0., 0., size.w, size.h);
        cr.fill();
    }
}

const BUTTON_TEXT: &'static str = "Click me";

// A simple button that knows when it has been clicked
#[derive(Default)]
struct Button {
    stub: WidgetStub,
    clicked: bool,
}

impl Button {
    // by this method the application can check if the button has been clicked
    fn has_been_clicked(&mut self) -> bool {
        let clicked = self.clicked;
        self.clicked = false;
        clicked
    }
}

impl Widget for Button {
    widget_stub!();

    // rendering the button
    fn exposed(&mut self, _expose: &ExposeArea, cr: &cairo::Context) {
        cr.set_source_rgb(0.7, 0.7, 0.7);
        let (x, y, w, h) = self.rect();
        cr.rectangle(x, y, w, h);
        cr.fill();

        cr.set_source_rgb(0., 0., 0.);
        cr.move_to(x+w/3., y+2.*h/3.);
        cr.select_font_face("Sans", cairo::FontSlant::Normal, cairo::FontWeight::Normal);
        cr.set_font_size(60.0);
        cr.show_text(BUTTON_TEXT);
        cr.fill();
    }

    // processing the event
    fn event(&mut self, ev: Event) -> Option<Event> {
        match ev.data {
            EventType::MouseButtonRelease(_) => {
                self.clicked = true;
                event_processed!()
            }
            _ => event_not_processed!()
        }.and_then(|p| p.pass_event(ev))
    }

    // signaling the minimal size of the button
    fn min_size(&self) -> Size {
        Size { w: 600., h: 100. }
    }
}

// The application function
fn app_execute() {
    // Initializing the UI and the interface to the windowing system
    let rw = Box::new(RootWidget::default());
    let mut view = PuglView::new(std::ptr::null_mut(), |pv| UI::new(pv, rw));
    let ui = view.handle();

    // creating the button
    let button = ui.new_widget(Box::new(Button::default()));

    // widget layouting
    ui.pack_to_layout(button, ui.root_layout(), StackDirection::Back);
    ui.do_layout();

    // showing the window
    ui.fit_window_size();
    ui.show_window();

    // event loop
    while !ui.close_request_issued() {
        ui.next_event(-1.0);

        // minimalist application logic
        //
        // We borrow the `button` widget from the `ui` and check if it has been clicked.
        if ui.widget(button).has_been_clicked() {
            println!("Button has been clicked.");
        }
    }
}

Modules

layout

Facilities for widget layouting

ui

The UI struct and widget management facilities

widget

Everything to describe an access a widget

Macros

event_not_processed
event_processed
widget_stub

Implements Widget::stub() and Widget::stub_mut()