gled-ui 2.32.1

gled's egui interface, shared by the desktop app and the browser
Documentation
//! What every editor canvas does around its own contents: the zoomable scene
//! inside the room the scrollbars leave, one painter for all of it, the
//! middle button and the scrollbars moving the view.

use crate::canvas::scene_modification::SceneModded;
use crate::canvas::scrollbars::{SCROLLBAR_WIDTH, pan_view, panning, scrollbars};
use crate::canvas::shapes::canvas_painter;
use crate::canvas::view::SceneView;
use egui::emath::TSTransform;
use egui::{Id, Margin, Painter, Rect, Response, Sense, Ui, UiBuilder, Vec2};

/// What a canvas is.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CanvasSpec {
    /// Salted with the host layer, so two canvases in one window keep their
    /// own widgets.
    pub(crate) id_salt: &'static str,
    /// The scene's extent, in scene units; it starts at the origin.
    pub(crate) scene_size: Vec2,
    /// How far in the user may zoom, in screen pixels per scene unit.
    pub(crate) max_zoom: f32,
    /// What the canvas reacts to; [`Sense::hover`] while a dialog floats over
    /// it.
    pub(crate) sense: Sense,
}

/// One frame of a canvas, as its contents see it.
pub(crate) struct CanvasFrame<'u> {
    /// The ui covering everything visible of the scene, in scene units.
    pub(crate) ui: &'u mut Ui,
    /// The canvas as one widget: clicks and drags on it, and its context
    /// menu. Its positions are in scene units.
    pub(crate) response: Response,
    /// The one layer the canvas paints into, in screen coordinates.
    pub(crate) painter: Painter,
    /// Scene units to screen pixels.
    pub(crate) scene_transform: TSTransform,
}

/// What showing a canvas leaves behind.
#[derive(Debug)]
pub(crate) struct CanvasShown {
    /// The zoom the view now shows the scene at.
    pub(crate) zoom: f32,
    /// Where the canvas is on screen, without the scrollbars.
    pub(crate) clip_rect: Rect,
}

/// Shows a canvas in the room left in `ui`, looking where `view` says, and
/// moves `view` by the middle button and the scrollbars.
pub(crate) fn show_canvas(
    ui: &mut Ui,
    spec: CanvasSpec,
    view: &mut SceneView,
    contents: impl FnOnce(&mut CanvasFrame<'_>),
) -> CanvasShown {
    // the scrollbars take a strip off the right and the bottom
    let clip_rect = ui.available_rect_before_wrap()
        - Margin {
            right: SCROLLBAR_WIDTH as i8,
            bottom: SCROLLBAR_WIDTH as i8,
            ..Default::default()
        };
    let host_layer = ui.layer_id();
    let scene = SceneModded::new()
        .scene_size(spec.scene_size)
        .max_zoom(spec.max_zoom);

    let pan = ui
        .scope_builder(UiBuilder::new().max_rect(clip_rect), |ui| {
            scene.show(ui, view, |parent_ui| {
                // Everything on screen is canvas, not just the scene: the clip
                // rect is the visible part of it, so clicking beside the scene
                // still deselects and a thing dragged past its edge stays
                // reachable.
                let visible = parent_ui.clip_rect();
                let mut child_ui = parent_ui.new_child(
                    UiBuilder::new()
                        .max_rect(visible)
                        .sense(spec.sense)
                        .id(Id::new((spec.id_salt, host_layer))),
                );
                child_ui.take_available_space();
                let response = child_ui.response();
                let painter = canvas_painter(clip_rect, &child_ui, host_layer);
                let scene_transform = child_ui
                    .ctx()
                    .layer_transform_to_global(child_ui.layer_id())
                    .unwrap_or_default();
                let mut frame = CanvasFrame {
                    ui: &mut child_ui,
                    response: response.clone(),
                    painter,
                    scene_transform,
                };
                contents(&mut frame);
                // the drag the middle button took off the contents, in scene
                // units
                if panning(&child_ui) {
                    response.drag_delta()
                } else {
                    Vec2::ZERO
                }
            })
        })
        .inner
        .inner;

    pan_view(view, pan);
    scrollbars(ui, clip_rect, &scene, view);
    CanvasShown {
        zoom: scene.zoom_of(clip_rect.size(), view),
        clip_rect,
    }
}