rust_widgets 2.9.2

Pure Rust cross-platform native GUI library with hardware-adaptive rendering, 180 widgets, touch/gesture support, i18n, and SVG-pipeline-accurate output
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT

//! Completion list and context menu.
//!
//! Split out of `render.rs` (principle #9). The entry point stays in the
//! parent; these are the paint helpers it calls.

use super::*;

impl CodeEditor {
    pub(super) fn draw_completion(&self, context: &mut RenderContext, chrome: &EditorChrome) {
        if !self.completion.visible {
            return;
        }
        let rect = self.completion_rect();
        let reveal = if self.config.fade_popups { self.popup_reveal } else { 1.0 };
        let face = chrome.popup_background.blend(&chrome.surface, (1.0 - reveal) * 0.9);
        let edge = chrome.popup_border.blend(&chrome.surface, (1.0 - reveal) * 0.9);
        context.face_with_gradient(
            rect,
            face,
            self.style().background_gradient.as_ref(),
            self.style().surface.unwrap_or_default(),
            self.style().border_radius.unwrap_or(0),
            Color::BLACK,
        );
        // The popup's edge follows its face's radius; a square outline left the corners bare.
        crate::widget::draw::stroke_surface_border(
            context,
            rect,
            self.style().border_radius.unwrap_or(0),
            edge,
            1,
        );
        let font = self.chrome_font();
        let line_box = context.measure_text("M", font).height as i32;
        let label_dy = (MIN_TOUCH_TARGET - line_box).max(0) / 2;
        // One buffer for the whole list: rebuilding the sigil and label with `format!`
        // allocated one or two `String`s per row per frame. Clearing and rewriting
        // reuses the capacity across rows and keeps the single draw call per row.
        use core::fmt::Write as _;
        let mut label = String::new();
        for offset in 0..self.completion_visible_rows() {
            let index = self.completion.scroll + offset;
            let Some(item) = self.completion.items.get(index) else { break };
            let row_y = rect.y + offset as i32 * MIN_TOUCH_TARGET;
            if index == self.completion.selected {
                context.fill_rect(
                    Rect::new(rect.x + 1, row_y, rect.width - 2, MIN_TOUCH_TARGET as u32),
                    chrome.popup_selected,
                );
            }
            label.clear();
            if let Some(kind) = item.kind {
                let _ = write!(label, "{} ", kind);
            }
            label.push_str(&item.label);
            if let Some(detail) = &item.detail {
                label.push_str("  ");
                label.push_str(detail);
            }
            // Vertically centred by the measured line box, not by a literal `+19`:
            // a row is `MIN_TOUCH_TARGET` tall and the face's box is a different height,
            // so the constant put the label near the row's bottom edge.
            context.draw_text(
                Point::new(rect.x + 8, row_y + label_dy),
                &label,
                font,
                chrome.ink,
                HorizontalAlignment::Left,
            );
        }
    }

    pub(super) fn draw_context_menu(&self, context: &mut RenderContext, chrome: &EditorChrome) {
        if !self.context_menu.visible {
            return;
        }
        let Some(rect) = self.context_menu_rect() else { return };
        let reveal = if self.config.fade_popups { self.popup_reveal } else { 1.0 };
        let face = chrome.popup_background.blend(&chrome.surface, (1.0 - reveal) * 0.9);
        let edge = chrome.popup_border.blend(&chrome.surface, (1.0 - reveal) * 0.9);
        context.face_with_gradient(
            rect,
            face,
            self.style().background_gradient.as_ref(),
            self.style().surface.unwrap_or_default(),
            self.style().border_radius.unwrap_or(0),
            Color::BLACK,
        );
        // The popup's edge follows its face's radius; a square outline left the corners bare.
        crate::widget::draw::stroke_surface_border(
            context,
            rect,
            self.style().border_radius.unwrap_or(0),
            edge,
            1,
        );
        let font = self.chrome_font();
        let line_box = context.measure_text("M", font).height as i32;
        let label_dy = (MIN_TOUCH_TARGET - line_box).max(0) / 2;
        for (index, item) in self.context_menu.items.iter().enumerate() {
            let row_y = rect.y + index as i32 * MIN_TOUCH_TARGET;
            if index == self.context_menu.selected && !item.disabled {
                context.fill_rect(
                    Rect::new(rect.x + 1, row_y, rect.width - 2, MIN_TOUCH_TARGET as u32),
                    chrome.popup_selected,
                );
            }
            // A disabled row is the dim ink; an enabled one is the resolved body ink.
            let color = if item.disabled { chrome.dim_ink } else { chrome.ink };
            context.draw_text(
                Point::new(rect.x + 8, row_y + label_dy),
                &item.label,
                font,
                color,
                HorizontalAlignment::Left,
            );
            if let Some(shortcut) = &item.shortcut {
                context.draw_text(
                    Point::new(rect.x + rect.width as i32 - 8, row_y + label_dy),
                    shortcut,
                    font,
                    chrome.dim_ink,
                    HorizontalAlignment::Right,
                );
            }
        }
    }
}