First commit. Version 0.1.0
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
//! Surface chrome: the client-side title bar and the Wayland input
|
||||
//! region. Both are backend-agnostic — the software and GPU draw
|
||||
//! paths call them with a `&mut Canvas` / a `&WlSurface` and get back
|
||||
//! the geometry they need for hit testing + commit.
|
||||
|
||||
use smithay_client_toolkit::compositor::{ CompositorState, Region };
|
||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
|
||||
|
||||
use crate::render::Canvas;
|
||||
use crate::types::{ Color, Rect };
|
||||
|
||||
/// Paint the client-side title bar (close button, divider, title text).
|
||||
///
|
||||
/// Returns the close button rect in physical pixels so the caller can
|
||||
/// store it for hit testing. Returns `Rect::default()` when `tb_h <=
|
||||
/// 0` — overlays / layer-shell surfaces have no title bar.
|
||||
pub( crate ) fn draw_titlebar( canvas: &mut Canvas, title: &str, pw: u32, tb_h: f32, sf: f32 ) -> Rect
|
||||
{
|
||||
if tb_h <= 0.0 { return Rect::default(); }
|
||||
let tb_rect = Rect { x: 0.0, y: 0.0, width: pw as f32, height: tb_h };
|
||||
canvas.fill_rect( tb_rect, Color::rgb( 0.15, 0.15, 0.18 ), 0.0 );
|
||||
|
||||
let title_size = 15.0;
|
||||
// `Canvas::draw_text` interprets `y` as the baseline. Match the
|
||||
// vertical-centring formula used by `Button::draw_text_button` so
|
||||
// chrome and content text lines line up at the same optical height.
|
||||
let title_y = ( tb_h + title_size * sf ) / 2.0 - 2.0;
|
||||
let title_w = canvas.measure_text( title, title_size );
|
||||
let title_x = ( pw as f32 - title_w ) / 2.0;
|
||||
canvas.draw_text( title, title_x, title_y, title_size, Color::WHITE );
|
||||
|
||||
let btn_size = tb_h - 8.0 * sf;
|
||||
let btn_x = pw as f32 - btn_size - 8.0 * sf;
|
||||
let btn_y = 4.0 * sf;
|
||||
let close_rect_phys = Rect { x: btn_x, y: btn_y, width: btn_size, height: btn_size };
|
||||
canvas.fill_rect( close_rect_phys, Color::rgba( 1.0, 1.0, 1.0, 0.1 ), 4.0 * sf );
|
||||
|
||||
let cx = btn_x + btn_size / 2.0;
|
||||
let cy = btn_y + btn_size / 2.0;
|
||||
let arm = btn_size * 0.25;
|
||||
canvas.draw_line( cx - arm, cy - arm, cx + arm, cy + arm, Color::WHITE, 2.0 * sf );
|
||||
canvas.draw_line( cx + arm, cy - arm, cx - arm, cy + arm, Color::WHITE, 2.0 * sf );
|
||||
|
||||
canvas.draw_line( 0.0, tb_h - 0.5, pw as f32, tb_h - 0.5, Color::rgba( 1.0, 1.0, 1.0, 0.15 ), 1.0 );
|
||||
|
||||
close_rect_phys
|
||||
}
|
||||
|
||||
/// Stamp a red warning banner at the top of the surface when the
|
||||
/// active theme came from the in-memory B/W fallback (i.e.
|
||||
/// [`crate::theme::is_fallback_active`] returned `true`). Gives the
|
||||
/// user a clear visual cue that `ltk-theme-default` is missing.
|
||||
///
|
||||
/// Paints a thin red strip across the top of the surface (24 px
|
||||
/// logical) with the install-instruction text. When the fallback is
|
||||
/// not active this is a no-op.
|
||||
pub( crate ) fn draw_fallback_banner(
|
||||
canvas: &mut Canvas,
|
||||
pw: u32,
|
||||
sf: f32,
|
||||
)
|
||||
{
|
||||
if !crate::theme::is_fallback_active() { return; }
|
||||
|
||||
let banner_h = 24.0 * sf;
|
||||
let rect = Rect { x: 0.0, y: 0.0, width: pw as f32, height: banner_h };
|
||||
canvas.fill_rect( rect, Color::rgb( 0.75, 0.10, 0.10 ), 0.0 );
|
||||
|
||||
let msg = "ltk: fallback theme — install `ltk-theme-default`";
|
||||
let size = 12.0;
|
||||
let msg_w = canvas.measure_text( msg, size );
|
||||
let x = ( ( pw as f32 - msg_w ) / 2.0 ).max( 6.0 * sf );
|
||||
let y = ( banner_h - size * sf ) / 2.0;
|
||||
canvas.draw_text( msg, x, y, size, Color::WHITE );
|
||||
}
|
||||
|
||||
/// Apply or clear the surface's input region (set by
|
||||
/// `App::input_region`). `None` restores the default of "receive
|
||||
/// input everywhere"; `Some(&[])` makes the surface pointer-through.
|
||||
pub( crate ) fn apply_input_region(
|
||||
wl_surface: &WlSurface,
|
||||
compositor: &CompositorState,
|
||||
input_region: Option<&[Rect]>,
|
||||
)
|
||||
{
|
||||
if let Some( regions ) = input_region
|
||||
{
|
||||
if let Ok( region ) = Region::new( compositor )
|
||||
{
|
||||
for r in regions
|
||||
{
|
||||
region.add( r.x as i32, r.y as i32, r.width as i32, r.height as i32 );
|
||||
}
|
||||
wl_surface.set_input_region( Some( region.wl_region() ) );
|
||||
}
|
||||
} else {
|
||||
wl_surface.set_input_region( None );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user