gled-ui 2.34.0

gled's egui interface, shared by the desktop app and the browser
Documentation
//! A clickable word in a rule's sentence: colored by what it changes and
//! underlined with dashes, so it reads as text yet looks editable.

use egui::{
    Color32, CursorIcon, Response, RichText, Sense, Shape, Stroke, TextStyle, Ui, WidgetText, vec2,
};

use crate::ui::windows::midi_controllers::rules::{Piece, TokenKind};

/// The color of a word that changes `kind`.
pub(crate) fn token_color(kind: TokenKind) -> Color32 {
    match kind {
        TokenKind::Main => Color32::from_rgb(94, 194, 122),
        TokenKind::Target => Color32::from_rgb(110, 168, 254),
        TokenKind::Effect | TokenKind::Setting | TokenKind::Animation | TokenKind::Parameter => {
            Color32::from_rgb(180, 140, 250)
        }
        TokenKind::Mode => Color32::from_rgb(240, 180, 80),
        TokenKind::Encoder | TokenKind::Beat | TokenKind::Blackout => {
            Color32::from_rgb(90, 200, 200)
        }
    }
}

/// A clickable word in `color`.
pub(crate) fn token(ui: &mut Ui, text: &str, color: Color32) -> Response {
    let galley =
        WidgetText::from(RichText::new(text)).into_galley(ui, None, f32::INFINITY, TextStyle::Body);
    let (rect, response) = ui.allocate_exact_size(galley.size() + vec2(0.0, 2.0), Sense::click());
    if ui.is_rect_visible(rect) {
        let color = if response.hovered() || response.has_focus() {
            color.gamma_multiply(1.3)
        } else {
            color
        };
        ui.painter().galley(rect.min, galley, color);
        let underline = rect.bottom() - 1.0;
        ui.painter().add(Shape::dashed_line(
            &[
                egui::pos2(rect.left(), underline),
                egui::pos2(rect.right(), underline),
            ],
            Stroke::new(1.0, color.gamma_multiply(0.7)),
            3.0,
            2.0,
        ));
    }
    response.on_hover_cursor(CursorIcon::PointingHand)
}

/// Draws a sentence; returns its words' responses.
pub(crate) fn sentence(ui: &mut Ui, pieces: &[Piece]) -> Vec<(TokenKind, Response)> {
    let mut tokens = Vec::new();
    for piece in pieces {
        match piece {
            Piece::Text(text) => {
                ui.label(text.trim());
            }
            Piece::Token { kind, text } => {
                tokens.push((*kind, token(ui, text, token_color(*kind))))
            }
        }
    }
    tokens
}