ltk/osk/
draw.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

use crate::render::Canvas;
use crate::types::{ Color, Rect };

use crate::Keysym;

use super::{ Action, Modifier, Panel };

const TEXT_SIZE:   f32 = 20.0;
const ACTION_SIZE: f32 = 14.0;
const ICON_SIZE:   f32 = 22.0;
const ARROW_SIZE:  f32 = 14.0;
const RADIUS:      f32 = 6.0;

/// The theme icon a key shows; its label is the fallback when the theme lacks it.
fn icon_for( key: &super::Key ) -> Option<&'static str>
{
	match ( &key.action, key.keysym )
	{
		( Some( Action::Shift ), _ )     => Some( "keyboard/shift" ),
		( Some( Action::Backspace ), _ ) => Some( "keyboard/backspace" ),
		( Some( Action::Enter ), _ )     => Some( "keyboard/enter" ),
		( Some( Action::Hide ), _ )      => Some( "general/down-simple" ),
		( Some( Action::Layout ), _ )    => Some( "accessibility/languages" ),
		( _, Some( Keysym::Left ) )      => Some( "general/left" ),
		( _, Some( Keysym::Right ) )     => Some( "general/right" ),
		( _, Some( Keysym::Up ) )        => Some( "general/up" ),
		( _, Some( Keysym::Down ) )      => Some( "general/down" ),
		_                                => None,
	}
}

impl Panel
{
	/// Paint the panel where [`Panel::set_rect`] put it.
	pub fn draw( &mut self, canvas: &mut Canvas )
	{
		let palette = crate::theme::palette();
		let scale   = self.scale;
		canvas.fill_rect( self.rect, palette.surface_alt, 0.0 );
		for i in 0..self.keys.len()
		{
			let ( rect, key ) = ( self.keys[ i ].rect, self.keys[ i ].key.clone() );
			let action  = key.action.is_some() || key.keysym.is_some() || key.modifier.is_some();
			let pressed = self.pressed() == Some( i );
			let held    = match key.modifier
			{
				Some( Modifier::Ctrl ) => self.modifiers().ctrl,
				Some( Modifier::Alt )  => self.modifiers().alt,
				None                   => key.action == Some( Action::Shift ) && self.shift_locked(),
			};
			let lit     = pressed || held;
			let bg      = if lit { palette.accent } else if action { palette.divider } else { palette.surface };
			let ink     = if lit { palette.bg } else { palette.text_primary };
			canvas.fill_rect( rect, bg, RADIUS * scale );
			let size = if key.keysym.is_some() { ARROW_SIZE } else { ICON_SIZE };
			let icon = icon_for( &key ).and_then( |name| self.icon( name, ( size * scale ) as u32, ink ) );
			match icon
			{
				Some( ( rgba, w, h ) ) =>
				{
					let dest = Rect { x: rect.x + ( rect.width - w as f32 ) / 2.0, y: rect.y + ( rect.height - h as f32 ) / 2.0, width: w as f32, height: h as f32 };
					canvas.draw_image_data( &rgba, w, h, dest, 1.0 );
				}
				None =>
				{
					let size = if action { ACTION_SIZE } else { TEXT_SIZE };
					draw_centred( canvas, &key.label, rect, size, ink );
				}
			}
		}
		if let Some( popup ) = &self.popup
		{
			canvas.fill_rect( popup.bounds, palette.surface, RADIUS * scale );
			canvas.stroke_rect( popup.bounds, palette.divider, 1.0, RADIUS * scale );
			for ( i, ( text, rect ) ) in popup.variants.iter().zip( &popup.rects ).enumerate()
			{
				let lit = popup.selected == Some( i );
				let bg  = if lit { palette.accent } else { palette.surface };
				let ink = if lit { palette.bg } else { palette.text_primary };
				canvas.fill_rect( *rect, bg, RADIUS * scale );
				draw_centred( canvas, text, *rect, TEXT_SIZE, ink );
			}
		}
	}

	/// The theme icon `name` at `size`, tinted `ink`; rasterised once per combination, a missing icon remembered as such.
	fn icon( &mut self, name: &'static str, size: u32, ink: Color ) -> Option<crate::ImageData>
	{
		let key = ( name, size, [ channel( ink.r ), channel( ink.g ), channel( ink.b ), channel( ink.a ) ] );
		self.icons.entry( key )
			.or_insert_with( || crate::theme_icon_tinted( name, size, ink ) )
			.clone()
	}
}

fn channel( v: f32 ) -> u8
{
	( v.clamp( 0.0, 1.0 ) * 255.0 ).round() as u8
}

/// `size` is in font space: the canvas multiplies it by its `dpi_scale`, as it does for every widget's text.
fn draw_centred( canvas: &mut Canvas, text: &str, rect: Rect, size: f32, color: Color )
{
	if text.is_empty() { return; }
	let w = canvas.measure_text( text, size );
	let glyph_h = size * canvas.dpi_scale();
	canvas.draw_text( text, rect.x + ( rect.width - w ) / 2.0, rect.y + rect.height / 2.0 + glyph_h * 0.35, size, color );
}