Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
345
third-party/vendor/sctk-adwaita/src/buttons.rs
vendored
Normal file
345
third-party/vendor/sctk-adwaita/src/buttons.rs
vendored
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
use smithay_client_toolkit::window::ButtonState;
|
||||
use tiny_skia::{FillRule, PathBuilder, PixmapMut, Rect, Stroke, Transform};
|
||||
|
||||
use crate::{
|
||||
theme::{ColorMap, BORDER_SIZE},
|
||||
Location, SkiaResult,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ButtonKind {
|
||||
Close,
|
||||
Maximize,
|
||||
Minimize,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Button {
|
||||
x: f32,
|
||||
y: f32,
|
||||
size: f32,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn radius(&self) -> f32 {
|
||||
self.size / 2.0
|
||||
}
|
||||
|
||||
pub fn x(&self) -> f32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
pub fn center_x(&self) -> f32 {
|
||||
self.x + self.radius()
|
||||
}
|
||||
|
||||
pub fn center_y(&self) -> f32 {
|
||||
self.y + self.radius()
|
||||
}
|
||||
|
||||
fn contains(&self, x: f32, y: f32) -> bool {
|
||||
x > self.x && x < self.x + self.size && y > self.y && y < self.y + self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn draw_minimize(
|
||||
&self,
|
||||
scale: f32,
|
||||
colors: &ColorMap,
|
||||
mouses: &[Location],
|
||||
pixmap: &mut PixmapMut,
|
||||
) -> SkiaResult {
|
||||
let btn_state = if mouses.contains(&Location::Button(ButtonKind::Minimize)) {
|
||||
ButtonState::Hovered
|
||||
} else {
|
||||
ButtonState::Idle
|
||||
};
|
||||
|
||||
let radius = self.radius();
|
||||
|
||||
let x = self.center_x();
|
||||
let y = self.center_y();
|
||||
|
||||
let circle = PathBuilder::from_circle(x, y, radius)?;
|
||||
|
||||
let button_bg = if btn_state == ButtonState::Hovered {
|
||||
colors.button_hover_paint()
|
||||
} else {
|
||||
colors.button_idle_paint()
|
||||
};
|
||||
|
||||
pixmap.fill_path(
|
||||
&circle,
|
||||
&button_bg,
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut button_icon_paint = colors.button_icon_paint();
|
||||
button_icon_paint.anti_alias = false;
|
||||
|
||||
let len = 8.0 * scale;
|
||||
let hlen = len / 2.0;
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(x - hlen, y + hlen, len, 1.0 * scale)?,
|
||||
&button_icon_paint,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn draw_maximize(
|
||||
&self,
|
||||
scale: f32,
|
||||
colors: &ColorMap,
|
||||
mouses: &[Location],
|
||||
maximizable: bool,
|
||||
is_maximized: bool,
|
||||
pixmap: &mut PixmapMut,
|
||||
) -> SkiaResult {
|
||||
let btn_state = if !maximizable {
|
||||
ButtonState::Disabled
|
||||
} else if mouses
|
||||
.iter()
|
||||
.any(|&l| l == Location::Button(ButtonKind::Maximize))
|
||||
{
|
||||
ButtonState::Hovered
|
||||
} else {
|
||||
ButtonState::Idle
|
||||
};
|
||||
|
||||
let radius = self.radius();
|
||||
|
||||
let x = self.center_x();
|
||||
let y = self.center_y();
|
||||
|
||||
let path1 = {
|
||||
let mut pb = PathBuilder::new();
|
||||
pb.push_circle(x, y, radius);
|
||||
pb.finish()?
|
||||
};
|
||||
|
||||
let button_bg = if btn_state == ButtonState::Hovered {
|
||||
colors.button_hover_paint()
|
||||
} else {
|
||||
colors.button_idle_paint()
|
||||
};
|
||||
|
||||
pixmap.fill_path(
|
||||
&path1,
|
||||
&button_bg,
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
let path2 = {
|
||||
let size = 8.0 * scale;
|
||||
let hsize = size / 2.0;
|
||||
let mut pb = PathBuilder::new();
|
||||
|
||||
let x = x - hsize;
|
||||
let y = y - hsize;
|
||||
pb.push_rect(x, y, size, size);
|
||||
|
||||
if is_maximized {
|
||||
if let Some(rect) = Rect::from_xywh(x + 2.0, y - 2.0, size, size) {
|
||||
pb.move_to(rect.left(), rect.top());
|
||||
pb.line_to(rect.right(), rect.top());
|
||||
pb.line_to(rect.right(), rect.bottom());
|
||||
}
|
||||
}
|
||||
|
||||
pb.finish()?
|
||||
};
|
||||
|
||||
let mut button_icon_paint = colors.button_icon_paint();
|
||||
button_icon_paint.anti_alias = false;
|
||||
pixmap.stroke_path(
|
||||
&path2,
|
||||
&button_icon_paint,
|
||||
&Stroke {
|
||||
width: 1.0 * scale,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn draw_close(
|
||||
&self,
|
||||
scale: f32,
|
||||
colors: &ColorMap,
|
||||
mouses: &[Location],
|
||||
pixmap: &mut PixmapMut,
|
||||
) -> SkiaResult {
|
||||
// Draw the close button
|
||||
let btn_state = if mouses
|
||||
.iter()
|
||||
.any(|&l| l == Location::Button(ButtonKind::Close))
|
||||
{
|
||||
ButtonState::Hovered
|
||||
} else {
|
||||
ButtonState::Idle
|
||||
};
|
||||
|
||||
let radius = self.radius();
|
||||
|
||||
let x = self.center_x();
|
||||
let y = self.center_y();
|
||||
|
||||
let path1 = {
|
||||
let mut pb = PathBuilder::new();
|
||||
pb.push_circle(x, y, radius);
|
||||
pb.finish()?
|
||||
};
|
||||
|
||||
let button_bg = if btn_state == ButtonState::Hovered {
|
||||
colors.button_hover_paint()
|
||||
} else {
|
||||
colors.button_idle_paint()
|
||||
};
|
||||
|
||||
pixmap.fill_path(
|
||||
&path1,
|
||||
&button_bg,
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
let x_icon = {
|
||||
let size = 3.5 * scale;
|
||||
let mut pb = PathBuilder::new();
|
||||
|
||||
{
|
||||
let sx = x - size;
|
||||
let sy = y - size;
|
||||
let ex = x + size;
|
||||
let ey = y + size;
|
||||
|
||||
pb.move_to(sx, sy);
|
||||
pb.line_to(ex, ey);
|
||||
pb.close();
|
||||
}
|
||||
|
||||
{
|
||||
let sx = x - size;
|
||||
let sy = y + size;
|
||||
let ex = x + size;
|
||||
let ey = y - size;
|
||||
|
||||
pb.move_to(sx, sy);
|
||||
pb.line_to(ex, ey);
|
||||
pb.close();
|
||||
}
|
||||
|
||||
pb.finish()?
|
||||
};
|
||||
|
||||
let mut button_icon_paint = colors.button_icon_paint();
|
||||
button_icon_paint.anti_alias = true;
|
||||
pixmap.stroke_path(
|
||||
&x_icon,
|
||||
&button_icon_paint,
|
||||
&Stroke {
|
||||
width: 1.1 * scale,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Buttons {
|
||||
pub close: Button,
|
||||
pub maximize: Button,
|
||||
pub minimize: Button,
|
||||
|
||||
w: u32,
|
||||
h: u32,
|
||||
|
||||
scale: u32,
|
||||
}
|
||||
|
||||
impl Default for Buttons {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
close: Default::default(),
|
||||
maximize: Default::default(),
|
||||
minimize: Default::default(),
|
||||
scale: 1,
|
||||
|
||||
w: 0,
|
||||
h: super::theme::HEADER_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Buttons {
|
||||
pub fn arrange(&mut self, w: u32) {
|
||||
self.w = w;
|
||||
|
||||
let scale = self.scale as f32;
|
||||
let margin_top = BORDER_SIZE as f32 * scale;
|
||||
let margin = 5.0 * scale;
|
||||
let spacing = 13.0 * scale;
|
||||
let size = 12.0 * 2.0 * scale;
|
||||
|
||||
let mut x = w as f32 * scale - margin - BORDER_SIZE as f32 * scale;
|
||||
let y = margin + margin_top;
|
||||
|
||||
x -= size;
|
||||
self.close.x = x;
|
||||
self.close.y = y;
|
||||
self.close.size = size;
|
||||
|
||||
x -= size;
|
||||
x -= spacing;
|
||||
self.maximize.x = x;
|
||||
self.maximize.y = y;
|
||||
self.maximize.size = size;
|
||||
|
||||
x -= size;
|
||||
x -= spacing;
|
||||
self.minimize.x = x;
|
||||
self.minimize.y = y;
|
||||
self.minimize.size = size;
|
||||
}
|
||||
|
||||
pub fn update_scale(&mut self, scale: u32) {
|
||||
if self.scale != scale {
|
||||
self.scale = scale;
|
||||
self.arrange(self.w);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_button(&self, x: f64, y: f64) -> Location {
|
||||
let x = x as f32 * self.scale as f32;
|
||||
let y = y as f32 * self.scale as f32;
|
||||
if self.close.contains(x, y) {
|
||||
Location::Button(ButtonKind::Close)
|
||||
} else if self.maximize.contains(x, y) {
|
||||
Location::Button(ButtonKind::Maximize)
|
||||
} else if self.minimize.contains(x, y) {
|
||||
Location::Button(ButtonKind::Minimize)
|
||||
} else {
|
||||
Location::Head
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scaled_size(&self) -> (u32, u32) {
|
||||
(self.w * self.scale, self.h * self.scale)
|
||||
}
|
||||
}
|
||||
24
third-party/vendor/sctk-adwaita/src/config.rs
vendored
Normal file
24
third-party/vendor/sctk-adwaita/src/config.rs
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//! System configuration.
|
||||
use std::process::Command;
|
||||
|
||||
/// Query system to see if dark theming should be preferred.
|
||||
pub(crate) fn prefer_dark() -> bool {
|
||||
// outputs something like: `variant variant uint32 1`
|
||||
let stdout = Command::new("dbus-send")
|
||||
.arg("--reply-timeout=100")
|
||||
.arg("--print-reply=literal")
|
||||
.arg("--dest=org.freedesktop.portal.Desktop")
|
||||
.arg("/org/freedesktop/portal/desktop")
|
||||
.arg("org.freedesktop.portal.Settings.Read")
|
||||
.arg("string:org.freedesktop.appearance")
|
||||
.arg("string:color-scheme")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|out| String::from_utf8(out.stdout).ok());
|
||||
|
||||
if matches!(stdout, Some(ref s) if s.is_empty()) {
|
||||
log::error!("XDG Settings Portal did not return response in time: timeout: 100ms, key: color-scheme");
|
||||
}
|
||||
|
||||
matches!(stdout, Some(s) if s.trim().ends_with("uint32 1"))
|
||||
}
|
||||
827
third-party/vendor/sctk-adwaita/src/lib.rs
vendored
Normal file
827
third-party/vendor/sctk-adwaita/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,827 @@
|
|||
mod buttons;
|
||||
mod config;
|
||||
mod parts;
|
||||
mod pointer;
|
||||
mod surface;
|
||||
pub mod theme;
|
||||
mod title;
|
||||
|
||||
use crate::theme::ColorMap;
|
||||
use buttons::{ButtonKind, Buttons};
|
||||
use client::{
|
||||
protocol::{wl_compositor, wl_seat, wl_shm, wl_subcompositor, wl_surface},
|
||||
Attached, DispatchData,
|
||||
};
|
||||
use parts::Parts;
|
||||
use pointer::PointerUserData;
|
||||
use smithay_client_toolkit::{
|
||||
reexports::client,
|
||||
seat::pointer::{ThemeManager, ThemeSpec, ThemedPointer},
|
||||
shm::AutoMemPool,
|
||||
window::{Frame, FrameRequest, State, WindowState},
|
||||
};
|
||||
use std::{cell::RefCell, fmt, rc::Rc};
|
||||
use theme::{ColorTheme, BORDER_SIZE, HEADER_SIZE};
|
||||
use tiny_skia::{
|
||||
ClipMask, Color, FillRule, Paint, Path, PathBuilder, Pixmap, PixmapMut, PixmapPaint, Point,
|
||||
Rect, Transform,
|
||||
};
|
||||
use title::TitleText;
|
||||
|
||||
type SkiaResult = Option<()>;
|
||||
|
||||
/*
|
||||
* Utilities
|
||||
*/
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Location {
|
||||
None,
|
||||
Head,
|
||||
Top,
|
||||
TopRight,
|
||||
Right,
|
||||
BottomRight,
|
||||
Bottom,
|
||||
BottomLeft,
|
||||
Left,
|
||||
TopLeft,
|
||||
Button(ButtonKind),
|
||||
}
|
||||
|
||||
/*
|
||||
* The core frame
|
||||
*/
|
||||
|
||||
struct Inner {
|
||||
parts: Parts,
|
||||
size: (u32, u32),
|
||||
resizable: bool,
|
||||
theme_over_surface: bool,
|
||||
implem: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
|
||||
maximized: bool,
|
||||
fullscreened: bool,
|
||||
tiled: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Inner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Inner")
|
||||
.field("parts", &self.parts)
|
||||
.field("size", &self.size)
|
||||
.field("resizable", &self.resizable)
|
||||
.field("theme_over_surface", &self.theme_over_surface)
|
||||
.field(
|
||||
"implem",
|
||||
&"FnMut(FrameRequest, u32, DispatchData) -> { ... }",
|
||||
)
|
||||
.field("maximized", &self.maximized)
|
||||
.field("fullscreened", &self.fullscreened)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn precise_location(buttons: &Buttons, old: Location, width: u32, x: f64, y: f64) -> Location {
|
||||
match old {
|
||||
Location::Head
|
||||
| Location::Button(_)
|
||||
| Location::Top
|
||||
| Location::TopLeft
|
||||
| Location::TopRight => match buttons.find_button(x, y) {
|
||||
Location::Head => {
|
||||
if y <= f64::from(BORDER_SIZE) {
|
||||
if x <= f64::from(BORDER_SIZE) {
|
||||
Location::TopLeft
|
||||
} else if x >= f64::from(width + BORDER_SIZE) {
|
||||
Location::TopRight
|
||||
} else {
|
||||
Location::Top
|
||||
}
|
||||
} else if x < f64::from(BORDER_SIZE) {
|
||||
Location::TopLeft
|
||||
} else if x > f64::from(width) {
|
||||
Location::TopRight
|
||||
} else {
|
||||
Location::Head
|
||||
}
|
||||
}
|
||||
other => other,
|
||||
},
|
||||
|
||||
Location::Bottom | Location::BottomLeft | Location::BottomRight => {
|
||||
if x <= f64::from(BORDER_SIZE) {
|
||||
Location::BottomLeft
|
||||
} else if x >= f64::from(width + BORDER_SIZE) {
|
||||
Location::BottomRight
|
||||
} else {
|
||||
Location::Bottom
|
||||
}
|
||||
}
|
||||
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameConfig {
|
||||
pub theme: ColorTheme,
|
||||
}
|
||||
|
||||
impl FrameConfig {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
theme: ColorTheme::auto(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
theme: ColorTheme::light(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
theme: ColorTheme::dark(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple set of decorations
|
||||
#[derive(Debug)]
|
||||
pub struct AdwaitaFrame {
|
||||
base_surface: wl_surface::WlSurface,
|
||||
compositor: Attached<wl_compositor::WlCompositor>,
|
||||
subcompositor: Attached<wl_subcompositor::WlSubcompositor>,
|
||||
inner: Rc<RefCell<Inner>>,
|
||||
pool: AutoMemPool,
|
||||
active: WindowState,
|
||||
hidden: bool,
|
||||
pointers: Vec<ThemedPointer>,
|
||||
themer: ThemeManager,
|
||||
surface_version: u32,
|
||||
|
||||
buttons: Rc<RefCell<Buttons>>,
|
||||
colors: ColorTheme,
|
||||
title: Option<String>,
|
||||
title_text: Option<TitleText>,
|
||||
}
|
||||
|
||||
impl Frame for AdwaitaFrame {
|
||||
type Error = ::std::io::Error;
|
||||
type Config = FrameConfig;
|
||||
fn init(
|
||||
base_surface: &wl_surface::WlSurface,
|
||||
compositor: &Attached<wl_compositor::WlCompositor>,
|
||||
subcompositor: &Attached<wl_subcompositor::WlSubcompositor>,
|
||||
shm: &Attached<wl_shm::WlShm>,
|
||||
theme_manager: Option<ThemeManager>,
|
||||
implementation: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
|
||||
) -> Result<AdwaitaFrame, ::std::io::Error> {
|
||||
let (themer, theme_over_surface) = if let Some(theme_manager) = theme_manager {
|
||||
(theme_manager, false)
|
||||
} else {
|
||||
(
|
||||
ThemeManager::init(ThemeSpec::System, compositor.clone(), shm.clone()),
|
||||
true,
|
||||
)
|
||||
};
|
||||
|
||||
let inner = Rc::new(RefCell::new(Inner {
|
||||
parts: Parts::default(),
|
||||
size: (1, 1),
|
||||
resizable: true,
|
||||
implem: implementation,
|
||||
theme_over_surface,
|
||||
maximized: false,
|
||||
fullscreened: false,
|
||||
tiled: false,
|
||||
}));
|
||||
|
||||
let pool = AutoMemPool::new(shm.clone())?;
|
||||
|
||||
let colors = ColorTheme::auto();
|
||||
|
||||
Ok(AdwaitaFrame {
|
||||
base_surface: base_surface.clone(),
|
||||
compositor: compositor.clone(),
|
||||
subcompositor: subcompositor.clone(),
|
||||
inner,
|
||||
pool,
|
||||
active: WindowState::Inactive,
|
||||
hidden: true,
|
||||
pointers: Vec::new(),
|
||||
themer,
|
||||
surface_version: compositor.as_ref().version(),
|
||||
buttons: Default::default(),
|
||||
title: None,
|
||||
title_text: TitleText::new(colors.active.font_color),
|
||||
colors,
|
||||
})
|
||||
}
|
||||
|
||||
fn new_seat(&mut self, seat: &Attached<wl_seat::WlSeat>) {
|
||||
let inner = self.inner.clone();
|
||||
|
||||
let buttons = self.buttons.clone();
|
||||
let pointer = self.themer.theme_pointer_with_impl(
|
||||
seat,
|
||||
move |event, pointer: ThemedPointer, ddata: DispatchData| {
|
||||
if let Some(data) = pointer
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<RefCell<PointerUserData>>()
|
||||
{
|
||||
let mut data = data.borrow_mut();
|
||||
let mut inner = inner.borrow_mut();
|
||||
data.event(event, &mut inner, &buttons.borrow(), &pointer, ddata);
|
||||
}
|
||||
},
|
||||
);
|
||||
pointer
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.set(|| RefCell::new(PointerUserData::new(seat.detach())));
|
||||
self.pointers.push(pointer);
|
||||
}
|
||||
|
||||
fn remove_seat(&mut self, seat: &wl_seat::WlSeat) {
|
||||
self.pointers.retain(|pointer| {
|
||||
pointer
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<RefCell<PointerUserData>>()
|
||||
.map(|user_data| {
|
||||
let guard = user_data.borrow_mut();
|
||||
if &guard.seat == seat {
|
||||
pointer.release();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
});
|
||||
}
|
||||
|
||||
fn set_states(&mut self, states: &[State]) -> bool {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let mut need_redraw = false;
|
||||
|
||||
// Process active.
|
||||
let new_active = if states.contains(&State::Activated) {
|
||||
WindowState::Active
|
||||
} else {
|
||||
WindowState::Inactive
|
||||
};
|
||||
need_redraw |= new_active != self.active;
|
||||
self.active = new_active;
|
||||
|
||||
// Process maximized.
|
||||
let new_maximized = states.contains(&State::Maximized);
|
||||
need_redraw |= new_maximized != inner.maximized;
|
||||
inner.maximized = new_maximized;
|
||||
|
||||
// Process fullscreened.
|
||||
let new_fullscreened = states.contains(&State::Fullscreen);
|
||||
need_redraw |= new_fullscreened != inner.fullscreened;
|
||||
inner.fullscreened = new_fullscreened;
|
||||
|
||||
let new_tiled = states.contains(&State::TiledLeft)
|
||||
|| states.contains(&State::TiledRight)
|
||||
|| states.contains(&State::TiledTop)
|
||||
|| states.contains(&State::TiledBottom);
|
||||
need_redraw |= new_tiled != inner.tiled;
|
||||
inner.tiled = new_tiled;
|
||||
|
||||
need_redraw
|
||||
}
|
||||
|
||||
fn set_hidden(&mut self, hidden: bool) {
|
||||
self.hidden = hidden;
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if !self.hidden {
|
||||
inner.parts.add_decorations(
|
||||
&self.base_surface,
|
||||
&self.compositor,
|
||||
&self.subcompositor,
|
||||
self.inner.clone(),
|
||||
);
|
||||
} else {
|
||||
inner.parts.remove_decorations();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_resizable(&mut self, resizable: bool) {
|
||||
self.inner.borrow_mut().resizable = resizable;
|
||||
}
|
||||
|
||||
fn resize(&mut self, newsize: (u32, u32)) {
|
||||
self.inner.borrow_mut().size = newsize;
|
||||
self.buttons
|
||||
.borrow_mut()
|
||||
.arrange(newsize.0 + BORDER_SIZE * 2);
|
||||
}
|
||||
|
||||
fn redraw(&mut self) {
|
||||
self.redraw_inner();
|
||||
}
|
||||
|
||||
fn subtract_borders(&self, width: i32, height: i32) -> (i32, i32) {
|
||||
if self.hidden || self.inner.borrow().fullscreened {
|
||||
(width, height)
|
||||
} else {
|
||||
(width, height - HEADER_SIZE as i32)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_borders(&self, width: i32, height: i32) -> (i32, i32) {
|
||||
if self.hidden || self.inner.borrow().fullscreened {
|
||||
(width, height)
|
||||
} else {
|
||||
(width, height + HEADER_SIZE as i32)
|
||||
}
|
||||
}
|
||||
|
||||
fn location(&self) -> (i32, i32) {
|
||||
if self.hidden || self.inner.borrow().fullscreened {
|
||||
(0, 0)
|
||||
} else {
|
||||
(0, -(HEADER_SIZE as i32))
|
||||
}
|
||||
}
|
||||
|
||||
fn set_config(&mut self, config: FrameConfig) {
|
||||
self.colors = config.theme;
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: String) {
|
||||
if let Some(title_text) = self.title_text.as_mut() {
|
||||
title_text.update_title(&title);
|
||||
}
|
||||
|
||||
self.title = Some(title);
|
||||
}
|
||||
}
|
||||
|
||||
impl AdwaitaFrame {
|
||||
fn redraw_inner(&mut self) -> SkiaResult {
|
||||
let inner = self.inner.borrow_mut();
|
||||
|
||||
// Don't draw borders if the frame explicitly hidden or fullscreened.
|
||||
if self.hidden || inner.fullscreened {
|
||||
inner.parts.hide_decorations();
|
||||
return Some(());
|
||||
}
|
||||
|
||||
// `parts` can't be empty here, since the initial state for `self.hidden` is true, and
|
||||
// they will be created once `self.hidden` will become `false`.
|
||||
let parts = &inner.parts;
|
||||
|
||||
let (width, height) = inner.size;
|
||||
|
||||
if let Some(decoration) = parts.decoration() {
|
||||
// Use header scale for all the thing.
|
||||
let header_scale = decoration.header.scale();
|
||||
self.buttons.borrow_mut().update_scale(header_scale);
|
||||
|
||||
let left_scale = decoration.left.scale();
|
||||
let right_scale = decoration.right.scale();
|
||||
let bottom_scale = decoration.bottom.scale();
|
||||
|
||||
let (header_width, header_height) = self.buttons.borrow().scaled_size();
|
||||
let header_height = header_height + BORDER_SIZE * header_scale;
|
||||
|
||||
{
|
||||
// Create the buffers and draw
|
||||
|
||||
let colors = if self.active == WindowState::Active {
|
||||
&self.colors.active
|
||||
} else {
|
||||
&self.colors.inactive
|
||||
};
|
||||
|
||||
if let Some(title_text) = self.title_text.as_mut() {
|
||||
title_text.update_color(colors.font_color);
|
||||
}
|
||||
|
||||
let border_paint = colors.border_paint();
|
||||
|
||||
// -> head-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
header_width as i32,
|
||||
header_height as i32,
|
||||
4 * header_width as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
let mut pixmap = PixmapMut::from_bytes(canvas, header_width, header_height)?;
|
||||
pixmap.fill(Color::TRANSPARENT);
|
||||
|
||||
if let Some(title_text) = self.title_text.as_mut() {
|
||||
title_text.update_scale(header_scale);
|
||||
}
|
||||
|
||||
draw_headerbar(
|
||||
&mut pixmap,
|
||||
self.title_text.as_ref().map(|t| t.pixmap()).unwrap_or(None),
|
||||
header_scale as f32,
|
||||
inner.resizable,
|
||||
inner.maximized,
|
||||
inner.tiled,
|
||||
self.active,
|
||||
&self.colors,
|
||||
&self.buttons.borrow(),
|
||||
&self
|
||||
.pointers
|
||||
.iter()
|
||||
.flat_map(|p| {
|
||||
if p.as_ref().is_alive() {
|
||||
let data: &RefCell<PointerUserData> =
|
||||
p.as_ref().user_data().get()?;
|
||||
Some(data.borrow().location)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Location>>(),
|
||||
);
|
||||
|
||||
decoration.header.subsurface.set_position(
|
||||
-(BORDER_SIZE as i32),
|
||||
-(HEADER_SIZE as i32 + BORDER_SIZE as i32),
|
||||
);
|
||||
decoration.header.surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
decoration.header.surface.damage_buffer(
|
||||
0,
|
||||
0,
|
||||
header_width as i32,
|
||||
header_height as i32,
|
||||
);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
decoration
|
||||
.header
|
||||
.surface
|
||||
.damage(0, 0, width as i32, HEADER_SIZE as i32);
|
||||
}
|
||||
decoration.header.surface.commit();
|
||||
}
|
||||
|
||||
if inner.maximized {
|
||||
// Don't draw the borders.
|
||||
decoration.hide_borders();
|
||||
return Some(());
|
||||
}
|
||||
|
||||
let w = ((width + 2 * BORDER_SIZE) * bottom_scale) as i32;
|
||||
let h = (BORDER_SIZE * bottom_scale) as i32;
|
||||
// -> bottom-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
w,
|
||||
h,
|
||||
(4 * bottom_scale * (width + 2 * BORDER_SIZE)) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
let mut pixmap = PixmapMut::from_bytes(canvas, w as u32, h as u32)?;
|
||||
pixmap.fill(Color::TRANSPARENT);
|
||||
|
||||
let size = 1.0;
|
||||
let x = BORDER_SIZE as f32 * bottom_scale as f32 - 1.0;
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(
|
||||
x,
|
||||
0.0,
|
||||
w as f32 - BORDER_SIZE as f32 * 2.0 * bottom_scale as f32 + 2.0,
|
||||
size,
|
||||
)?,
|
||||
&border_paint,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
decoration
|
||||
.bottom
|
||||
.subsurface
|
||||
.set_position(-(BORDER_SIZE as i32), height as i32);
|
||||
decoration.bottom.surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
decoration.bottom.surface.damage_buffer(
|
||||
0,
|
||||
0,
|
||||
((width + 2 * BORDER_SIZE) * bottom_scale) as i32,
|
||||
(BORDER_SIZE * bottom_scale) as i32,
|
||||
);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
decoration.bottom.surface.damage(
|
||||
0,
|
||||
0,
|
||||
(width + 2 * BORDER_SIZE) as i32,
|
||||
BORDER_SIZE as i32,
|
||||
);
|
||||
}
|
||||
decoration.bottom.surface.commit();
|
||||
}
|
||||
|
||||
let w = (BORDER_SIZE * left_scale) as i32;
|
||||
let h = (height * left_scale) as i32;
|
||||
// -> left-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
w,
|
||||
h,
|
||||
4 * (BORDER_SIZE * left_scale) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
let mut bg = Paint::default();
|
||||
bg.set_color_rgba8(255, 0, 0, 255);
|
||||
|
||||
let mut pixmap = PixmapMut::from_bytes(canvas, w as u32, h as u32)?;
|
||||
pixmap.fill(Color::TRANSPARENT);
|
||||
|
||||
let size = 1.0;
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(w as f32 - size, 0.0, w as f32, h as f32)?,
|
||||
&border_paint,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
decoration
|
||||
.left
|
||||
.subsurface
|
||||
.set_position(-(BORDER_SIZE as i32), 0);
|
||||
decoration.left.surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
decoration.left.surface.damage_buffer(0, 0, w, h);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
decoration.left.surface.damage(
|
||||
0,
|
||||
0,
|
||||
BORDER_SIZE as i32,
|
||||
(height + HEADER_SIZE) as i32,
|
||||
);
|
||||
}
|
||||
decoration.left.surface.commit();
|
||||
}
|
||||
|
||||
let w = (BORDER_SIZE * right_scale) as i32;
|
||||
let h = (height * right_scale) as i32;
|
||||
// -> right-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
w,
|
||||
h,
|
||||
4 * (BORDER_SIZE * right_scale) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
let mut bg = Paint::default();
|
||||
bg.set_color_rgba8(255, 0, 0, 255);
|
||||
|
||||
let mut pixmap = PixmapMut::from_bytes(canvas, w as u32, h as u32)?;
|
||||
pixmap.fill(Color::TRANSPARENT);
|
||||
|
||||
let size = 1.0;
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(0.0, 0.0, size, h as f32)?,
|
||||
&border_paint,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
decoration.right.subsurface.set_position(width as i32, 0);
|
||||
decoration.right.surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
decoration.right.surface.damage_buffer(0, 0, w, h);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
decoration
|
||||
.right
|
||||
.surface
|
||||
.damage(0, 0, BORDER_SIZE as i32, height as i32);
|
||||
}
|
||||
decoration.right.surface.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AdwaitaFrame {
|
||||
fn drop(&mut self) {
|
||||
for ptr in self.pointers.drain(..) {
|
||||
if ptr.as_ref().version() >= 3 {
|
||||
ptr.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_headerbar(
|
||||
pixmap: &mut PixmapMut,
|
||||
text_pixmap: Option<&Pixmap>,
|
||||
scale: f32,
|
||||
maximizable: bool,
|
||||
is_maximized: bool,
|
||||
tiled: bool,
|
||||
state: WindowState,
|
||||
colors: &ColorTheme,
|
||||
buttons: &Buttons,
|
||||
mouses: &[Location],
|
||||
) {
|
||||
let border_size = BORDER_SIZE as f32 * scale;
|
||||
|
||||
let margin_h = border_size;
|
||||
let margin_v = border_size;
|
||||
|
||||
let colors = colors.for_state(state);
|
||||
|
||||
draw_headerbar_bg(
|
||||
pixmap,
|
||||
scale,
|
||||
margin_h,
|
||||
margin_v,
|
||||
colors,
|
||||
is_maximized,
|
||||
tiled,
|
||||
);
|
||||
|
||||
if let Some(text_pixmap) = text_pixmap {
|
||||
let canvas_w = pixmap.width() as f32;
|
||||
let canvas_h = pixmap.height() as f32;
|
||||
|
||||
let header_w = canvas_w - margin_h * 2.0;
|
||||
let header_h = canvas_h - margin_v;
|
||||
|
||||
let text_w = text_pixmap.width() as f32;
|
||||
let text_h = text_pixmap.height() as f32;
|
||||
|
||||
let x = header_w / 2.0 - text_w / 2.0;
|
||||
let y = header_h / 2.0 - text_h / 2.0;
|
||||
|
||||
let x = margin_h + x;
|
||||
let y = margin_v + y;
|
||||
|
||||
let (x, y) = if x + text_w < buttons.minimize.x() - 10.0 {
|
||||
(x, y)
|
||||
} else {
|
||||
let y = header_h / 2.0 - text_h / 2.0;
|
||||
|
||||
let x = buttons.minimize.x() - text_w - 10.0;
|
||||
let y = margin_v + y;
|
||||
(x, y)
|
||||
};
|
||||
|
||||
let x = x.max(margin_h + 5.0);
|
||||
|
||||
if let Some(clip) = Rect::from_xywh(0.0, 0.0, buttons.minimize.x() - 10.0, canvas_h) {
|
||||
let mut mask = ClipMask::new();
|
||||
mask.set_path(
|
||||
canvas_w as u32,
|
||||
canvas_h as u32,
|
||||
&PathBuilder::from_rect(clip),
|
||||
FillRule::Winding,
|
||||
false,
|
||||
);
|
||||
pixmap.draw_pixmap(
|
||||
x as i32,
|
||||
y as i32,
|
||||
text_pixmap.as_ref(),
|
||||
&PixmapPaint::default(),
|
||||
Transform::identity(),
|
||||
Some(&mask),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if buttons.close.x() > margin_h {
|
||||
buttons.close.draw_close(scale, colors, mouses, pixmap);
|
||||
}
|
||||
|
||||
if buttons.maximize.x() > margin_h {
|
||||
buttons
|
||||
.maximize
|
||||
.draw_maximize(scale, colors, mouses, maximizable, is_maximized, pixmap);
|
||||
}
|
||||
|
||||
if buttons.minimize.x() > margin_h {
|
||||
buttons
|
||||
.minimize
|
||||
.draw_minimize(scale, colors, mouses, pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_headerbar_bg(
|
||||
pixmap: &mut PixmapMut,
|
||||
scale: f32,
|
||||
margin_h: f32,
|
||||
margin_v: f32,
|
||||
colors: &ColorMap,
|
||||
is_maximized: bool,
|
||||
tiled: bool,
|
||||
) -> SkiaResult {
|
||||
let w = pixmap.width() as f32;
|
||||
let h = pixmap.height() as f32;
|
||||
|
||||
let radius = if is_maximized || tiled {
|
||||
0.0
|
||||
} else {
|
||||
10.0 * scale
|
||||
};
|
||||
|
||||
let margin_h = margin_h - 1.0;
|
||||
let w = w - margin_h * 2.0;
|
||||
|
||||
let bg = rounded_headerbar_shape(margin_h, margin_v, w, h, radius)?;
|
||||
|
||||
pixmap.fill_path(
|
||||
&bg,
|
||||
&colors.headerbar_paint(),
|
||||
FillRule::Winding,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(margin_h, h - 1.0, w, h)?,
|
||||
&colors.border_paint(),
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn rounded_headerbar_shape(x: f32, y: f32, width: f32, height: f32, radius: f32) -> Option<Path> {
|
||||
use std::f32::consts::FRAC_1_SQRT_2;
|
||||
|
||||
let mut pb = PathBuilder::new();
|
||||
let mut cursor = Point::from_xy(x, y);
|
||||
|
||||
// !!!
|
||||
// This code is heavily "inspired" by https://gitlab.com/snakedye/snui/
|
||||
// So technically it should be licensed under MPL-2.0, sorry about that 🥺 👉👈
|
||||
// !!!
|
||||
|
||||
// Positioning the cursor
|
||||
cursor.y += radius;
|
||||
pb.move_to(cursor.x, cursor.y);
|
||||
|
||||
// Drawing the outline
|
||||
pb.cubic_to(
|
||||
cursor.x,
|
||||
cursor.y,
|
||||
cursor.x,
|
||||
cursor.y - FRAC_1_SQRT_2 * radius,
|
||||
{
|
||||
cursor.x += radius;
|
||||
cursor.x
|
||||
},
|
||||
{
|
||||
cursor.y -= radius;
|
||||
cursor.y
|
||||
},
|
||||
);
|
||||
pb.line_to(
|
||||
{
|
||||
cursor.x = x + width - radius;
|
||||
cursor.x
|
||||
},
|
||||
cursor.y,
|
||||
);
|
||||
pb.cubic_to(
|
||||
cursor.x,
|
||||
cursor.y,
|
||||
cursor.x + FRAC_1_SQRT_2 * radius,
|
||||
cursor.y,
|
||||
{
|
||||
cursor.x += radius;
|
||||
cursor.x
|
||||
},
|
||||
{
|
||||
cursor.y += radius;
|
||||
cursor.y
|
||||
},
|
||||
);
|
||||
pb.line_to(cursor.x, {
|
||||
cursor.y = y + height;
|
||||
cursor.y
|
||||
});
|
||||
pb.line_to(
|
||||
{
|
||||
cursor.x = x;
|
||||
cursor.x
|
||||
},
|
||||
cursor.y,
|
||||
);
|
||||
|
||||
pb.close();
|
||||
|
||||
pb.finish()
|
||||
}
|
||||
198
third-party/vendor/sctk-adwaita/src/parts.rs
vendored
Normal file
198
third-party/vendor/sctk-adwaita/src/parts.rs
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use smithay_client_toolkit::{
|
||||
reexports::client::{
|
||||
protocol::{
|
||||
wl_compositor::WlCompositor, wl_subcompositor::WlSubcompositor,
|
||||
wl_subsurface::WlSubsurface, wl_surface::WlSurface,
|
||||
},
|
||||
Attached, DispatchData,
|
||||
},
|
||||
window::FrameRequest,
|
||||
};
|
||||
|
||||
use crate::{surface, Inner, Location};
|
||||
|
||||
pub enum DecorationPartKind {
|
||||
Header,
|
||||
Top,
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Decoration {
|
||||
pub header: Part,
|
||||
|
||||
pub top: Part,
|
||||
pub left: Part,
|
||||
pub right: Part,
|
||||
pub bottom: Part,
|
||||
}
|
||||
|
||||
impl Decoration {
|
||||
pub fn iter(&self) -> [&Part; 5] {
|
||||
[
|
||||
&self.header,
|
||||
&self.top,
|
||||
&self.left,
|
||||
&self.right,
|
||||
&self.bottom,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn hide_decoration(&self) {
|
||||
for p in self.iter() {
|
||||
p.surface.attach(None, 0, 0);
|
||||
p.surface.commit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hide_borders(&self) {
|
||||
for p in self.iter().iter().skip(1) {
|
||||
p.surface.attach(None, 0, 0);
|
||||
p.surface.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Parts {
|
||||
decoration: Option<Decoration>,
|
||||
}
|
||||
|
||||
impl Parts {
|
||||
pub fn add_decorations(
|
||||
&mut self,
|
||||
parent: &WlSurface,
|
||||
compositor: &Attached<WlCompositor>,
|
||||
subcompositor: &Attached<WlSubcompositor>,
|
||||
inner: Rc<RefCell<Inner>>,
|
||||
) {
|
||||
if self.decoration.is_none() {
|
||||
let header = Part::new(parent, compositor, subcompositor, Some(inner));
|
||||
let top = Part::new(parent, compositor, subcompositor, None);
|
||||
let left = Part::new(parent, compositor, subcompositor, None);
|
||||
let right = Part::new(parent, compositor, subcompositor, None);
|
||||
let bottom = Part::new(parent, compositor, subcompositor, None);
|
||||
|
||||
self.decoration = Some(Decoration {
|
||||
header,
|
||||
top,
|
||||
left,
|
||||
right,
|
||||
bottom,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_decorations(&mut self) {
|
||||
self.decoration = None;
|
||||
}
|
||||
|
||||
pub fn hide_decorations(&self) {
|
||||
if let Some(decor) = self.decoration.as_ref() {
|
||||
decor.hide_decoration();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decoration(&self) -> Option<&Decoration> {
|
||||
self.decoration.as_ref()
|
||||
}
|
||||
|
||||
pub fn find_decoration_part(&self, surface: &WlSurface) -> DecorationPartKind {
|
||||
if let Some(decor) = self.decoration() {
|
||||
if surface.as_ref().equals(decor.header.surface.as_ref()) {
|
||||
DecorationPartKind::Header
|
||||
} else if surface.as_ref().equals(decor.top.surface.as_ref()) {
|
||||
DecorationPartKind::Top
|
||||
} else if surface.as_ref().equals(decor.bottom.surface.as_ref()) {
|
||||
DecorationPartKind::Bottom
|
||||
} else if surface.as_ref().equals(decor.left.surface.as_ref()) {
|
||||
DecorationPartKind::Left
|
||||
} else if surface.as_ref().equals(decor.right.surface.as_ref()) {
|
||||
DecorationPartKind::Right
|
||||
} else {
|
||||
DecorationPartKind::None
|
||||
}
|
||||
} else {
|
||||
DecorationPartKind::None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_surface(&self, surface: &WlSurface) -> Location {
|
||||
if let Some(decor) = self.decoration() {
|
||||
if surface.as_ref().equals(decor.header.surface.as_ref()) {
|
||||
Location::Head
|
||||
} else if surface.as_ref().equals(decor.top.surface.as_ref()) {
|
||||
Location::Top
|
||||
} else if surface.as_ref().equals(decor.bottom.surface.as_ref()) {
|
||||
Location::Bottom
|
||||
} else if surface.as_ref().equals(decor.left.surface.as_ref()) {
|
||||
Location::Left
|
||||
} else if surface.as_ref().equals(decor.right.surface.as_ref()) {
|
||||
Location::Right
|
||||
} else {
|
||||
Location::None
|
||||
}
|
||||
} else {
|
||||
Location::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Part {
|
||||
pub surface: WlSurface,
|
||||
pub subsurface: WlSubsurface,
|
||||
}
|
||||
|
||||
impl Part {
|
||||
fn new(
|
||||
parent: &WlSurface,
|
||||
compositor: &Attached<WlCompositor>,
|
||||
subcompositor: &Attached<WlSubcompositor>,
|
||||
inner: Option<Rc<RefCell<Inner>>>,
|
||||
) -> Part {
|
||||
let surface = if let Some(inner) = inner {
|
||||
surface::setup_surface(
|
||||
compositor.create_surface(),
|
||||
Some(move |dpi, surface: WlSurface, ddata: DispatchData| {
|
||||
surface.set_buffer_scale(dpi);
|
||||
surface.commit();
|
||||
(inner.borrow_mut().implem)(FrameRequest::Refresh, 0, ddata);
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
surface::setup_surface(
|
||||
compositor.create_surface(),
|
||||
Some(move |dpi, surface: WlSurface, _ddata: DispatchData| {
|
||||
surface.set_buffer_scale(dpi);
|
||||
surface.commit();
|
||||
}),
|
||||
)
|
||||
};
|
||||
|
||||
let surface = surface.detach();
|
||||
|
||||
let subsurface = subcompositor.get_subsurface(&surface, parent);
|
||||
|
||||
Part {
|
||||
surface,
|
||||
subsurface: subsurface.detach(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale(&self) -> u32 {
|
||||
surface::get_surface_scale_factor(&self.surface) as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Part {
|
||||
fn drop(&mut self) {
|
||||
self.subsurface.destroy();
|
||||
self.surface.destroy();
|
||||
}
|
||||
}
|
||||
260
third-party/vendor/sctk-adwaita/src/pointer.rs
vendored
Normal file
260
third-party/vendor/sctk-adwaita/src/pointer.rs
vendored
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
use log::error;
|
||||
use smithay_client_toolkit::{
|
||||
reexports::{
|
||||
client::{
|
||||
protocol::{wl_pointer, wl_seat::WlSeat},
|
||||
DispatchData,
|
||||
},
|
||||
protocols::xdg_shell::client::xdg_toplevel::ResizeEdge,
|
||||
},
|
||||
seat::pointer::ThemedPointer,
|
||||
window::FrameRequest,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
buttons::{ButtonKind, Buttons},
|
||||
parts::DecorationPartKind,
|
||||
precise_location,
|
||||
theme::{BORDER_SIZE, HEADER_SIZE},
|
||||
Inner, Location,
|
||||
};
|
||||
|
||||
pub(crate) struct PointerUserData {
|
||||
pub location: Location,
|
||||
current_surface: DecorationPartKind,
|
||||
|
||||
position: (f64, f64),
|
||||
pub seat: WlSeat,
|
||||
last_click: Option<std::time::Instant>,
|
||||
|
||||
lpm_grab: Option<ButtonKind>,
|
||||
}
|
||||
|
||||
impl PointerUserData {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
Self {
|
||||
location: Location::None,
|
||||
current_surface: DecorationPartKind::None,
|
||||
position: (0.0, 0.0),
|
||||
seat,
|
||||
last_click: None,
|
||||
lpm_grab: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event(
|
||||
&mut self,
|
||||
event: wl_pointer::Event,
|
||||
inner: &mut Inner,
|
||||
buttons: &Buttons,
|
||||
pointer: &ThemedPointer,
|
||||
ddata: DispatchData<'_>,
|
||||
) {
|
||||
use wl_pointer::Event;
|
||||
match event {
|
||||
Event::Enter {
|
||||
serial,
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
self.location = precise_location(
|
||||
buttons,
|
||||
inner.parts.find_surface(&surface),
|
||||
inner.size.0,
|
||||
surface_x,
|
||||
surface_y,
|
||||
);
|
||||
self.current_surface = inner.parts.find_decoration_part(&surface);
|
||||
self.position = (surface_x, surface_y);
|
||||
change_pointer(pointer, inner, self.location, Some(serial))
|
||||
}
|
||||
Event::Leave { serial, .. } => {
|
||||
self.current_surface = DecorationPartKind::None;
|
||||
|
||||
self.location = Location::None;
|
||||
change_pointer(pointer, inner, self.location, Some(serial));
|
||||
(inner.implem)(FrameRequest::Refresh, 0, ddata);
|
||||
}
|
||||
Event::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
self.position = (surface_x, surface_y);
|
||||
let newpos =
|
||||
precise_location(buttons, self.location, inner.size.0, surface_x, surface_y);
|
||||
if newpos != self.location {
|
||||
match (newpos, self.location) {
|
||||
(Location::Button(_), _) | (_, Location::Button(_)) => {
|
||||
// pointer movement involves a button, request refresh
|
||||
(inner.implem)(FrameRequest::Refresh, 0, ddata);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
// we changed of part of the decoration, pointer image
|
||||
// may need to be changed
|
||||
self.location = newpos;
|
||||
change_pointer(pointer, inner, self.location, None)
|
||||
}
|
||||
}
|
||||
Event::Button {
|
||||
serial,
|
||||
button,
|
||||
state,
|
||||
..
|
||||
} => {
|
||||
let request = if state == wl_pointer::ButtonState::Pressed {
|
||||
match button {
|
||||
// Left mouse button.
|
||||
0x110 => lmb_press(self, inner.maximized, inner.resizable),
|
||||
// Right mouse button.
|
||||
0x111 => rmb_press(self),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
// Left mouse button.
|
||||
if button == 0x110 {
|
||||
lmb_release(self, inner.maximized)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(request) = request {
|
||||
(inner.implem)(request, serial, ddata);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lmb_press(
|
||||
pointer_data: &mut PointerUserData,
|
||||
maximized: bool,
|
||||
resizable: bool,
|
||||
) -> Option<FrameRequest> {
|
||||
match pointer_data.location {
|
||||
Location::Top if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::Top,
|
||||
)),
|
||||
Location::TopLeft if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::TopLeft,
|
||||
)),
|
||||
Location::Left if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::Left,
|
||||
)),
|
||||
Location::BottomLeft if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::BottomLeft,
|
||||
)),
|
||||
Location::Bottom if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::Bottom,
|
||||
)),
|
||||
Location::BottomRight if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::BottomRight,
|
||||
)),
|
||||
Location::Right if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::Right,
|
||||
)),
|
||||
Location::TopRight if resizable => Some(FrameRequest::Resize(
|
||||
pointer_data.seat.clone(),
|
||||
ResizeEdge::TopRight,
|
||||
)),
|
||||
Location::Head => {
|
||||
let last_click = pointer_data.last_click.replace(std::time::Instant::now());
|
||||
|
||||
if let Some(last) = last_click {
|
||||
if last.elapsed() < std::time::Duration::from_millis(400) {
|
||||
pointer_data.last_click = None;
|
||||
|
||||
if maximized {
|
||||
Some(FrameRequest::UnMaximize)
|
||||
} else {
|
||||
Some(FrameRequest::Maximize)
|
||||
}
|
||||
} else {
|
||||
Some(FrameRequest::Move(pointer_data.seat.clone()))
|
||||
}
|
||||
} else {
|
||||
Some(FrameRequest::Move(pointer_data.seat.clone()))
|
||||
}
|
||||
}
|
||||
Location::Button(btn) => {
|
||||
pointer_data.lpm_grab = Some(btn);
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn lmb_release(pointer_data: &mut PointerUserData, maximized: bool) -> Option<FrameRequest> {
|
||||
let lpm_grab = pointer_data.lpm_grab.take();
|
||||
|
||||
match pointer_data.location {
|
||||
Location::Button(btn) => {
|
||||
if lpm_grab == Some(btn) {
|
||||
let req = match btn {
|
||||
ButtonKind::Close => FrameRequest::Close,
|
||||
ButtonKind::Maximize => {
|
||||
if maximized {
|
||||
FrameRequest::UnMaximize
|
||||
} else {
|
||||
FrameRequest::Maximize
|
||||
}
|
||||
}
|
||||
ButtonKind::Minimize => FrameRequest::Minimize,
|
||||
};
|
||||
|
||||
Some(req)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn rmb_press(pointer_data: &PointerUserData) -> Option<FrameRequest> {
|
||||
match pointer_data.location {
|
||||
Location::Head | Location::Button(_) => Some(FrameRequest::ShowMenu(
|
||||
pointer_data.seat.clone(),
|
||||
pointer_data.position.0 as i32 - BORDER_SIZE as i32,
|
||||
// We must offset it by header size for precise position.
|
||||
pointer_data.position.1 as i32 - (HEADER_SIZE as i32 + BORDER_SIZE as i32),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn change_pointer(pointer: &ThemedPointer, inner: &Inner, location: Location, serial: Option<u32>) {
|
||||
// Prevent theming of the surface if it was requested.
|
||||
if !inner.theme_over_surface && location == Location::None {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = match location {
|
||||
// If we can't resize a frame we shouldn't show resize cursors.
|
||||
_ if !inner.resizable => "left_ptr",
|
||||
Location::Top => "top_side",
|
||||
Location::TopRight => "top_right_corner",
|
||||
Location::Right => "right_side",
|
||||
Location::BottomRight => "bottom_right_corner",
|
||||
Location::Bottom => "bottom_side",
|
||||
Location::BottomLeft => "bottom_left_corner",
|
||||
Location::Left => "left_side",
|
||||
Location::TopLeft => "top_left_corner",
|
||||
_ => "left_ptr",
|
||||
};
|
||||
|
||||
if pointer.set_cursor(name, serial).is_err() {
|
||||
error!("Failed to set cursor");
|
||||
}
|
||||
}
|
||||
154
third-party/vendor/sctk-adwaita/src/surface.rs
vendored
Normal file
154
third-party/vendor/sctk-adwaita/src/surface.rs
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
use std::{cell::RefCell, rc::Rc, sync::Mutex};
|
||||
|
||||
use super::client;
|
||||
use smithay_client_toolkit as sctk;
|
||||
|
||||
use client::{
|
||||
protocol::{wl_output, wl_surface},
|
||||
Attached, DispatchData, Main,
|
||||
};
|
||||
use sctk::output::{add_output_listener, with_output_info, OutputListener};
|
||||
|
||||
pub(crate) struct SurfaceUserData {
|
||||
scale_factor: i32,
|
||||
outputs: Vec<(wl_output::WlOutput, i32, OutputListener)>,
|
||||
}
|
||||
|
||||
impl SurfaceUserData {
|
||||
fn new() -> Self {
|
||||
SurfaceUserData {
|
||||
scale_factor: 1,
|
||||
outputs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enter<F>(
|
||||
&mut self,
|
||||
output: wl_output::WlOutput,
|
||||
surface: wl_surface::WlSurface,
|
||||
callback: &Option<Rc<RefCell<F>>>,
|
||||
) where
|
||||
F: FnMut(i32, wl_surface::WlSurface, DispatchData) + 'static,
|
||||
{
|
||||
let output_scale = with_output_info(&output, |info| info.scale_factor).unwrap_or(1);
|
||||
let my_surface = surface.clone();
|
||||
// Use a UserData to safely share the callback with the other thread
|
||||
let my_callback = client::UserData::new();
|
||||
if let Some(ref cb) = callback {
|
||||
my_callback.set(|| cb.clone());
|
||||
}
|
||||
let listener = add_output_listener(&output, move |output, info, ddata| {
|
||||
let mut user_data = my_surface
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<SurfaceUserData>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
// update the scale factor of the relevant output
|
||||
for (ref o, ref mut factor, _) in user_data.outputs.iter_mut() {
|
||||
if o.as_ref().equals(output.as_ref()) {
|
||||
if info.obsolete {
|
||||
// an output that no longer exists is marked by a scale factor of -1
|
||||
*factor = -1;
|
||||
} else {
|
||||
*factor = info.scale_factor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// recompute the scale factor with the new info
|
||||
let callback = my_callback.get::<Rc<RefCell<F>>>().cloned();
|
||||
let old_scale_factor = user_data.scale_factor;
|
||||
let new_scale_factor = user_data.recompute_scale_factor();
|
||||
drop(user_data);
|
||||
if let Some(ref cb) = callback {
|
||||
if old_scale_factor != new_scale_factor {
|
||||
(*cb.borrow_mut())(new_scale_factor, surface.clone(), ddata);
|
||||
}
|
||||
}
|
||||
});
|
||||
self.outputs.push((output, output_scale, listener));
|
||||
}
|
||||
|
||||
pub(crate) fn leave(&mut self, output: &wl_output::WlOutput) {
|
||||
self.outputs
|
||||
.retain(|(ref output2, _, _)| !output.as_ref().equals(output2.as_ref()));
|
||||
}
|
||||
|
||||
fn recompute_scale_factor(&mut self) -> i32 {
|
||||
let mut new_scale_factor = 1;
|
||||
self.outputs.retain(|&(_, output_scale, _)| {
|
||||
if output_scale > 0 {
|
||||
new_scale_factor = ::std::cmp::max(new_scale_factor, output_scale);
|
||||
true
|
||||
} else {
|
||||
// cleanup obsolete output
|
||||
false
|
||||
}
|
||||
});
|
||||
if self.outputs.is_empty() {
|
||||
// don't update the scale factor if we are not displayed on any output
|
||||
return self.scale_factor;
|
||||
}
|
||||
self.scale_factor = new_scale_factor;
|
||||
new_scale_factor
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_surface<F>(
|
||||
surface: Main<wl_surface::WlSurface>,
|
||||
callback: Option<F>,
|
||||
) -> Attached<wl_surface::WlSurface>
|
||||
where
|
||||
F: FnMut(i32, wl_surface::WlSurface, DispatchData) + 'static,
|
||||
{
|
||||
let callback = callback.map(|c| Rc::new(RefCell::new(c)));
|
||||
surface.quick_assign(move |surface, event, ddata| {
|
||||
let mut user_data = surface
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<SurfaceUserData>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
match event {
|
||||
wl_surface::Event::Enter { output } => {
|
||||
// Passing the callback to be added to output listener
|
||||
user_data.enter(output, surface.detach(), &callback);
|
||||
}
|
||||
wl_surface::Event::Leave { output } => {
|
||||
user_data.leave(&output);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let old_scale_factor = user_data.scale_factor;
|
||||
let new_scale_factor = user_data.recompute_scale_factor();
|
||||
drop(user_data);
|
||||
if let Some(ref cb) = callback {
|
||||
if old_scale_factor != new_scale_factor {
|
||||
(*cb.borrow_mut())(new_scale_factor, surface.detach(), ddata);
|
||||
}
|
||||
}
|
||||
});
|
||||
surface
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.set_threadsafe(|| Mutex::new(SurfaceUserData::new()));
|
||||
surface.into()
|
||||
}
|
||||
|
||||
/// Returns the current suggested scale factor of a surface.
|
||||
///
|
||||
/// Panics if the surface was not created using `Environment::create_surface` or
|
||||
/// `Environment::create_surface_with_dpi_callback`.
|
||||
pub fn get_surface_scale_factor(surface: &wl_surface::WlSurface) -> i32 {
|
||||
surface
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<SurfaceUserData>>()
|
||||
.expect("SCTK: Surface was not created by SCTK.")
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scale_factor
|
||||
}
|
||||
133
third-party/vendor/sctk-adwaita/src/theme.rs
vendored
Normal file
133
third-party/vendor/sctk-adwaita/src/theme.rs
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use smithay_client_toolkit::window::WindowState;
|
||||
|
||||
pub use tiny_skia::Color;
|
||||
use tiny_skia::{Paint, Shader};
|
||||
|
||||
pub(crate) const BORDER_SIZE: u32 = 10;
|
||||
pub(crate) const HEADER_SIZE: u32 = 35;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ColorMap {
|
||||
pub headerbar: Color,
|
||||
pub button_idle: Color,
|
||||
pub button_hover: Color,
|
||||
pub button_icon: Color,
|
||||
pub border_color: Color,
|
||||
pub font_color: Color,
|
||||
}
|
||||
|
||||
impl ColorMap {
|
||||
pub(crate) fn headerbar_paint(&self) -> Paint {
|
||||
Paint {
|
||||
shader: Shader::SolidColor(self.headerbar),
|
||||
anti_alias: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn button_idle_paint(&self) -> Paint {
|
||||
Paint {
|
||||
shader: Shader::SolidColor(self.button_idle),
|
||||
anti_alias: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn button_hover_paint(&self) -> Paint {
|
||||
Paint {
|
||||
shader: Shader::SolidColor(self.button_hover),
|
||||
anti_alias: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn button_icon_paint(&self) -> Paint {
|
||||
Paint {
|
||||
shader: Shader::SolidColor(self.button_icon),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn border_paint(&self) -> Paint {
|
||||
Paint {
|
||||
shader: Shader::SolidColor(self.border_color),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ColorTheme {
|
||||
pub active: ColorMap,
|
||||
pub inactive: ColorMap,
|
||||
}
|
||||
|
||||
impl Default for ColorTheme {
|
||||
fn default() -> Self {
|
||||
Self::light()
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorTheme {
|
||||
/// Automatically choose between light & dark themes based on:
|
||||
/// * dbus org.freedesktop.portal.Settings
|
||||
/// <https://flatpak.github.io/xdg-desktop-portal/#gdbus-interface-org-freedesktop-portal-Settings>
|
||||
pub fn auto() -> Self {
|
||||
match crate::config::prefer_dark() {
|
||||
true => Self::dark(),
|
||||
false => Self::light(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
active: ColorMap {
|
||||
headerbar: Color::from_rgba8(235, 235, 235, 255),
|
||||
button_idle: Color::from_rgba8(216, 216, 216, 255),
|
||||
button_hover: Color::from_rgba8(207, 207, 207, 255),
|
||||
button_icon: Color::from_rgba8(42, 42, 42, 255),
|
||||
border_color: Color::from_rgba8(220, 220, 220, 255),
|
||||
font_color: Color::from_rgba8(47, 47, 47, 255),
|
||||
},
|
||||
inactive: ColorMap {
|
||||
headerbar: Color::from_rgba8(250, 250, 250, 255),
|
||||
button_idle: Color::from_rgba8(240, 240, 240, 255),
|
||||
button_hover: Color::from_rgba8(216, 216, 216, 255),
|
||||
button_icon: Color::from_rgba8(148, 148, 148, 255),
|
||||
border_color: Color::from_rgba8(220, 220, 220, 255),
|
||||
font_color: Color::from_rgba8(150, 150, 150, 255),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
active: ColorMap {
|
||||
headerbar: Color::from_rgba8(48, 48, 48, 255),
|
||||
button_idle: Color::from_rgba8(69, 69, 69, 255),
|
||||
button_hover: Color::from_rgba8(79, 79, 79, 255),
|
||||
button_icon: Color::from_rgba8(255, 255, 255, 255),
|
||||
border_color: Color::from_rgba8(58, 58, 58, 255),
|
||||
font_color: Color::from_rgba8(255, 255, 255, 255),
|
||||
},
|
||||
inactive: ColorMap {
|
||||
headerbar: Color::from_rgba8(36, 36, 36, 255),
|
||||
button_idle: Color::from_rgba8(47, 47, 47, 255),
|
||||
button_hover: Color::from_rgba8(57, 57, 57, 255),
|
||||
button_icon: Color::from_rgba8(144, 144, 144, 255),
|
||||
border_color: Color::from_rgba8(58, 58, 58, 255),
|
||||
font_color: Color::from_rgba8(144, 144, 144, 255),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorTheme {
|
||||
pub(crate) fn for_state(&self, state: WindowState) -> &ColorMap {
|
||||
if state == WindowState::Active {
|
||||
&self.active
|
||||
} else {
|
||||
&self.inactive
|
||||
}
|
||||
}
|
||||
}
|
||||
61
third-party/vendor/sctk-adwaita/src/title.rs
vendored
Normal file
61
third-party/vendor/sctk-adwaita/src/title.rs
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use tiny_skia::{Color, Pixmap};
|
||||
|
||||
#[cfg(any(feature = "crossfont", feature = "ab_glyph"))]
|
||||
mod config;
|
||||
#[cfg(any(feature = "crossfont", feature = "ab_glyph"))]
|
||||
mod font_preference;
|
||||
|
||||
#[cfg(feature = "crossfont")]
|
||||
mod crossfont_renderer;
|
||||
|
||||
#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
|
||||
mod ab_glyph_renderer;
|
||||
|
||||
#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
|
||||
mod dumb;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TitleText {
|
||||
#[cfg(feature = "crossfont")]
|
||||
imp: crossfont_renderer::CrossfontTitleText,
|
||||
#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
|
||||
imp: ab_glyph_renderer::AbGlyphTitleText,
|
||||
#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
|
||||
imp: dumb::DumbTitleText,
|
||||
}
|
||||
|
||||
impl TitleText {
|
||||
pub fn new(color: Color) -> Option<Self> {
|
||||
#[cfg(feature = "crossfont")]
|
||||
return crossfont_renderer::CrossfontTitleText::new(color)
|
||||
.ok()
|
||||
.map(|imp| Self { imp });
|
||||
|
||||
#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
|
||||
return Some(Self {
|
||||
imp: ab_glyph_renderer::AbGlyphTitleText::new(color),
|
||||
});
|
||||
|
||||
#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
|
||||
{
|
||||
let _ = color;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_scale(&mut self, scale: u32) {
|
||||
self.imp.update_scale(scale)
|
||||
}
|
||||
|
||||
pub fn update_title<S: Into<String>>(&mut self, title: S) {
|
||||
self.imp.update_title(title)
|
||||
}
|
||||
|
||||
pub fn update_color(&mut self, color: Color) {
|
||||
self.imp.update_color(color)
|
||||
}
|
||||
|
||||
pub fn pixmap(&self) -> Option<&Pixmap> {
|
||||
self.imp.pixmap()
|
||||
}
|
||||
}
|
||||
BIN
third-party/vendor/sctk-adwaita/src/title/Cantarell-Regular.ttf
vendored
Normal file
BIN
third-party/vendor/sctk-adwaita/src/title/Cantarell-Regular.ttf
vendored
Normal file
Binary file not shown.
177
third-party/vendor/sctk-adwaita/src/title/ab_glyph_renderer.rs
vendored
Normal file
177
third-party/vendor/sctk-adwaita/src/title/ab_glyph_renderer.rs
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
//! Title renderer using ab_glyph.
|
||||
//!
|
||||
//! Requires no dynamically linked dependencies.
|
||||
//!
|
||||
//! Can fallback to a embedded Cantarell-Regular.ttf font (SIL Open Font Licence v1.1)
|
||||
//! if the system font doesn't work.
|
||||
use crate::title::{config, font_preference::FontPreference};
|
||||
use ab_glyph::{point, Font, FontRef, Glyph, PxScale, PxScaleFont, ScaleFont, VariableFont};
|
||||
use std::{fs::File, process::Command};
|
||||
use tiny_skia::{Color, Pixmap, PremultipliedColorU8};
|
||||
|
||||
const CANTARELL: &[u8] = include_bytes!("Cantarell-Regular.ttf");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AbGlyphTitleText {
|
||||
title: String,
|
||||
font: Option<(memmap2::Mmap, FontPreference)>,
|
||||
original_px_size: f32,
|
||||
size: PxScale,
|
||||
color: Color,
|
||||
pixmap: Option<Pixmap>,
|
||||
}
|
||||
|
||||
impl AbGlyphTitleText {
|
||||
pub fn new(color: Color) -> Self {
|
||||
let font_pref = config::titlebar_font().unwrap_or_default();
|
||||
let font_pref_pt_size = font_pref.pt_size;
|
||||
let font = font_file_matching(&font_pref)
|
||||
.and_then(|f| mmap(&f))
|
||||
.map(|mmap| (mmap, font_pref));
|
||||
|
||||
let size = parse_font(&font)
|
||||
.pt_to_px_scale(font_pref_pt_size)
|
||||
.expect("invalid font units_per_em");
|
||||
|
||||
Self {
|
||||
title: <_>::default(),
|
||||
font,
|
||||
original_px_size: size.x,
|
||||
size,
|
||||
color,
|
||||
pixmap: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_scale(&mut self, scale: u32) {
|
||||
let new_scale = PxScale::from(self.original_px_size * scale as f32);
|
||||
if (self.size.x - new_scale.x).abs() > f32::EPSILON {
|
||||
self.size = new_scale;
|
||||
self.pixmap = self.render();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_title(&mut self, title: impl Into<String>) {
|
||||
let new_title = title.into();
|
||||
if new_title != self.title {
|
||||
self.title = new_title;
|
||||
self.pixmap = self.render();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_color(&mut self, color: Color) {
|
||||
if color != self.color {
|
||||
self.color = color;
|
||||
self.pixmap = self.render();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pixmap(&self) -> Option<&Pixmap> {
|
||||
self.pixmap.as_ref()
|
||||
}
|
||||
|
||||
/// Render returning the new `Pixmap`.
|
||||
fn render(&self) -> Option<Pixmap> {
|
||||
let font = parse_font(&self.font);
|
||||
let font = font.as_scaled(self.size);
|
||||
|
||||
let glyphs = self.layout(&font);
|
||||
let last_glyph = glyphs.last()?;
|
||||
let width = (last_glyph.position.x + font.h_advance(last_glyph.id)).ceil() as u32;
|
||||
let height = font.height().ceil() as u32;
|
||||
|
||||
let mut pixmap = Pixmap::new(width, height)?;
|
||||
|
||||
let pixels = pixmap.pixels_mut();
|
||||
|
||||
for glyph in glyphs {
|
||||
if let Some(outline) = font.outline_glyph(glyph) {
|
||||
let bounds = outline.px_bounds();
|
||||
let left = bounds.min.x as u32;
|
||||
let top = bounds.min.y as u32;
|
||||
outline.draw(|x, y, c| {
|
||||
let p_idx = (top + y) * width + (left + x);
|
||||
let old_alpha_u8 = pixels[p_idx as usize].alpha();
|
||||
let new_alpha = c + (old_alpha_u8 as f32 / 255.0);
|
||||
if let Some(px) = PremultipliedColorU8::from_rgba(
|
||||
(self.color.red() * new_alpha * 255.0) as _,
|
||||
(self.color.green() * new_alpha * 255.0) as _,
|
||||
(self.color.blue() * new_alpha * 255.0) as _,
|
||||
(new_alpha * 255.0) as _,
|
||||
) {
|
||||
pixels[p_idx as usize] = px;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Some(pixmap)
|
||||
}
|
||||
|
||||
/// Simple single-line glyph layout.
|
||||
fn layout(&self, font: &PxScaleFont<impl Font>) -> Vec<Glyph> {
|
||||
let mut caret = point(0.0, font.ascent());
|
||||
let mut last_glyph: Option<Glyph> = None;
|
||||
let mut target = Vec::new();
|
||||
for c in self.title.chars() {
|
||||
if c.is_control() {
|
||||
continue;
|
||||
}
|
||||
let mut glyph = font.scaled_glyph(c);
|
||||
if let Some(previous) = last_glyph.take() {
|
||||
caret.x += font.kern(previous.id, glyph.id);
|
||||
}
|
||||
glyph.position = caret;
|
||||
|
||||
last_glyph = Some(glyph.clone());
|
||||
caret.x += font.h_advance(glyph.id);
|
||||
|
||||
target.push(glyph);
|
||||
}
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the memmapped system font or fallback to built-in cantarell.
|
||||
fn parse_font(sys_font: &Option<(memmap2::Mmap, FontPreference)>) -> FontRef<'_> {
|
||||
match sys_font {
|
||||
Some((mmap, font_pref)) => {
|
||||
FontRef::try_from_slice(mmap)
|
||||
.map(|mut f| {
|
||||
// basic "bold" handling for variable fonts
|
||||
if font_pref
|
||||
.style
|
||||
.as_deref()
|
||||
.map_or(false, |s| s.eq_ignore_ascii_case("bold"))
|
||||
{
|
||||
f.set_variation(b"wght", 700.0);
|
||||
}
|
||||
f
|
||||
})
|
||||
.unwrap_or_else(|_| FontRef::try_from_slice(CANTARELL).unwrap())
|
||||
}
|
||||
_ => FontRef::try_from_slice(CANTARELL).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Font-config without dynamically linked dependencies
|
||||
fn font_file_matching(pref: &FontPreference) -> Option<File> {
|
||||
let mut pattern = pref.name.clone();
|
||||
if let Some(style) = &pref.style {
|
||||
pattern.push(':');
|
||||
pattern.push_str(style);
|
||||
}
|
||||
Command::new("fc-match")
|
||||
.arg("-f")
|
||||
.arg("%{file}")
|
||||
.arg(&pattern)
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|out| String::from_utf8(out.stdout).ok())
|
||||
.and_then(|path| File::open(path.trim()).ok())
|
||||
}
|
||||
|
||||
fn mmap(file: &File) -> Option<memmap2::Mmap> {
|
||||
// Safety: System font files are not expected to be mutated during use
|
||||
unsafe { memmap2::Mmap::map(file).ok() }
|
||||
}
|
||||
20
third-party/vendor/sctk-adwaita/src/title/config.rs
vendored
Normal file
20
third-party/vendor/sctk-adwaita/src/title/config.rs
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//! System font configuration.
|
||||
use crate::title::font_preference::FontPreference;
|
||||
use std::process::Command;
|
||||
|
||||
/// Query system for which font to use for window titles.
|
||||
pub(crate) fn titlebar_font() -> Option<FontPreference> {
|
||||
// outputs something like: `'Cantarell Bold 12'`
|
||||
let stdout = Command::new("gsettings")
|
||||
.args(["get", "org.gnome.desktop.wm.preferences", "titlebar-font"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|out| String::from_utf8(out.stdout).ok())?;
|
||||
|
||||
FontPreference::from_name_style_size(
|
||||
stdout
|
||||
.trim()
|
||||
.trim_end_matches('\'')
|
||||
.trim_start_matches('\''),
|
||||
)
|
||||
}
|
||||
227
third-party/vendor/sctk-adwaita/src/title/crossfont_renderer.rs
vendored
Normal file
227
third-party/vendor/sctk-adwaita/src/title/crossfont_renderer.rs
vendored
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
use crate::title::config;
|
||||
use crossfont::{GlyphKey, Rasterize, RasterizedGlyph};
|
||||
use tiny_skia::{Color, Pixmap, PixmapPaint, PixmapRef, Transform};
|
||||
|
||||
pub struct CrossfontTitleText {
|
||||
title: String,
|
||||
|
||||
font_desc: crossfont::FontDesc,
|
||||
font_key: crossfont::FontKey,
|
||||
size: crossfont::Size,
|
||||
scale: u32,
|
||||
metrics: crossfont::Metrics,
|
||||
rasterizer: crossfont::Rasterizer,
|
||||
color: Color,
|
||||
|
||||
pixmap: Option<Pixmap>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CrossfontTitleText {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TitleText")
|
||||
.field("title", &self.title)
|
||||
.field("font_desc", &self.font_desc)
|
||||
.field("font_key", &self.font_key)
|
||||
.field("size", &self.size)
|
||||
.field("scale", &self.scale)
|
||||
.field("pixmap", &self.pixmap)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl CrossfontTitleText {
|
||||
pub fn new(color: Color) -> Result<Self, crossfont::Error> {
|
||||
let title = "".into();
|
||||
let scale = 1;
|
||||
|
||||
let font_pref = config::titlebar_font().unwrap_or_default();
|
||||
let font_style = font_pref
|
||||
.style
|
||||
.map(crossfont::Style::Specific)
|
||||
.unwrap_or_else(|| crossfont::Style::Description {
|
||||
slant: crossfont::Slant::Normal,
|
||||
weight: crossfont::Weight::Normal,
|
||||
});
|
||||
let font_desc = crossfont::FontDesc::new(&font_pref.name, font_style);
|
||||
|
||||
let mut rasterizer = crossfont::Rasterizer::new(scale as f32)?;
|
||||
let size = crossfont::Size::new(font_pref.pt_size);
|
||||
let font_key = rasterizer.load_font(&font_desc, size)?;
|
||||
|
||||
// Need to load at least one glyph for the face before calling metrics.
|
||||
// The glyph requested here ('m' at the time of writing) has no special
|
||||
// meaning.
|
||||
rasterizer.get_glyph(GlyphKey {
|
||||
font_key,
|
||||
character: 'm',
|
||||
size,
|
||||
})?;
|
||||
|
||||
let metrics = rasterizer.metrics(font_key, size)?;
|
||||
|
||||
let mut this = Self {
|
||||
title,
|
||||
font_desc,
|
||||
font_key,
|
||||
size,
|
||||
scale,
|
||||
metrics,
|
||||
rasterizer,
|
||||
color,
|
||||
pixmap: None,
|
||||
};
|
||||
|
||||
this.rerender();
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
fn update_metrics(&mut self) -> Result<(), crossfont::Error> {
|
||||
self.rasterizer.get_glyph(GlyphKey {
|
||||
font_key: self.font_key,
|
||||
character: 'm',
|
||||
size: self.size,
|
||||
})?;
|
||||
self.metrics = self.rasterizer.metrics(self.font_key, self.size)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_scale(&mut self, scale: u32) {
|
||||
if self.scale != scale {
|
||||
self.rasterizer.update_dpr(scale as f32);
|
||||
self.scale = scale;
|
||||
|
||||
self.update_metrics().ok();
|
||||
|
||||
self.rerender();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_title<S: Into<String>>(&mut self, title: S) {
|
||||
let title = title.into();
|
||||
if self.title != title {
|
||||
self.title = title;
|
||||
self.rerender();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_color(&mut self, color: Color) {
|
||||
if self.color != color {
|
||||
self.color = color;
|
||||
self.rerender();
|
||||
}
|
||||
}
|
||||
|
||||
fn rerender(&mut self) {
|
||||
let glyphs: Vec<_> = self
|
||||
.title
|
||||
.chars()
|
||||
.filter_map(|character| {
|
||||
let key = GlyphKey {
|
||||
character,
|
||||
font_key: self.font_key,
|
||||
size: self.size,
|
||||
};
|
||||
|
||||
self.rasterizer
|
||||
.get_glyph(key)
|
||||
.map(|glyph| (key, glyph))
|
||||
.ok()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if glyphs.is_empty() {
|
||||
self.pixmap = None;
|
||||
return;
|
||||
}
|
||||
|
||||
let width = self.calc_width(&glyphs);
|
||||
let height = self.metrics.line_height.round() as i32;
|
||||
|
||||
let mut pixmap = if let Some(p) = Pixmap::new(width as u32, height as u32) {
|
||||
p
|
||||
} else {
|
||||
self.pixmap = None;
|
||||
return;
|
||||
};
|
||||
// pixmap.fill(Color::from_rgba8(255, 0, 0, 55));
|
||||
|
||||
let mut caret = 0;
|
||||
let mut last_glyph = None;
|
||||
|
||||
for (key, glyph) in glyphs {
|
||||
let mut buffer = Vec::with_capacity(glyph.width as usize * 4);
|
||||
|
||||
let glyph_buffer = match &glyph.buffer {
|
||||
crossfont::BitmapBuffer::Rgb(v) => v.chunks(3),
|
||||
crossfont::BitmapBuffer::Rgba(v) => v.chunks(4),
|
||||
};
|
||||
|
||||
for px in glyph_buffer {
|
||||
let alpha = if let Some(alpha) = px.get(3) {
|
||||
*alpha as f32 / 255.0
|
||||
} else {
|
||||
let r = px[0] as f32 / 255.0;
|
||||
let g = px[1] as f32 / 255.0;
|
||||
let b = px[2] as f32 / 255.0;
|
||||
(r + g + b) / 3.0
|
||||
};
|
||||
|
||||
let mut color = self.color;
|
||||
color.set_alpha(alpha);
|
||||
let color = color.premultiply().to_color_u8();
|
||||
|
||||
buffer.push(color.red());
|
||||
buffer.push(color.green());
|
||||
buffer.push(color.blue());
|
||||
buffer.push(color.alpha());
|
||||
}
|
||||
|
||||
if let Some(last) = last_glyph {
|
||||
let (x, _) = self.rasterizer.kerning(last, key);
|
||||
caret += x as i32;
|
||||
}
|
||||
|
||||
if let Some(pixmap_glyph) =
|
||||
PixmapRef::from_bytes(&buffer, glyph.width as _, glyph.height as _)
|
||||
{
|
||||
pixmap.draw_pixmap(
|
||||
glyph.left + caret,
|
||||
height - glyph.top + self.metrics.descent.round() as i32,
|
||||
pixmap_glyph,
|
||||
&PixmapPaint::default(),
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
caret += glyph.advance.0;
|
||||
|
||||
last_glyph = Some(key);
|
||||
}
|
||||
|
||||
self.pixmap = Some(pixmap);
|
||||
}
|
||||
|
||||
pub fn pixmap(&self) -> Option<&Pixmap> {
|
||||
self.pixmap.as_ref()
|
||||
}
|
||||
|
||||
fn calc_width(&mut self, glyphs: &[(GlyphKey, RasterizedGlyph)]) -> i32 {
|
||||
let mut caret = 0;
|
||||
let mut last_glyph: Option<&GlyphKey> = None;
|
||||
|
||||
for (key, glyph) in glyphs.iter() {
|
||||
if let Some(last) = last_glyph {
|
||||
let (x, _) = self.rasterizer.kerning(*last, *key);
|
||||
caret += x as i32;
|
||||
}
|
||||
|
||||
caret += glyph.advance.0;
|
||||
|
||||
last_glyph = Some(key);
|
||||
}
|
||||
|
||||
caret
|
||||
}
|
||||
}
|
||||
16
third-party/vendor/sctk-adwaita/src/title/dumb.rs
vendored
Normal file
16
third-party/vendor/sctk-adwaita/src/title/dumb.rs
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use tiny_skia::{Color, Pixmap};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DumbTitleText {}
|
||||
|
||||
impl DumbTitleText {
|
||||
pub fn update_scale(&mut self, _scale: u32) {}
|
||||
|
||||
pub fn update_title<S: Into<String>>(&mut self, _title: S) {}
|
||||
|
||||
pub fn update_color(&mut self, _color: Color) {}
|
||||
|
||||
pub fn pixmap(&self) -> Option<&Pixmap> {
|
||||
None
|
||||
}
|
||||
}
|
||||
91
third-party/vendor/sctk-adwaita/src/title/font_preference.rs
vendored
Normal file
91
third-party/vendor/sctk-adwaita/src/title/font_preference.rs
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#[derive(Debug)]
|
||||
pub(crate) struct FontPreference {
|
||||
pub name: String,
|
||||
pub style: Option<String>,
|
||||
pub pt_size: f32,
|
||||
}
|
||||
|
||||
impl Default for FontPreference {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "sans-serif".into(),
|
||||
style: None,
|
||||
pt_size: 10.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FontPreference {
|
||||
/// Parse config string like `Cantarell 12`, `Cantarell Bold 11`, `Noto Serif CJK HK Bold 12`.
|
||||
pub fn from_name_style_size(conf: &str) -> Option<Self> {
|
||||
// assume last is size, 2nd last is style and the rest is name.
|
||||
match conf.rsplit_once(' ') {
|
||||
Some((head, tail)) if tail.chars().all(|c| c.is_numeric()) => {
|
||||
let pt_size: f32 = tail.parse().unwrap_or(10.0);
|
||||
match head.rsplit_once(' ') {
|
||||
Some((name, style)) if !name.is_empty() => Some(Self {
|
||||
name: name.into(),
|
||||
style: Some(style.into()),
|
||||
pt_size,
|
||||
}),
|
||||
None if !head.is_empty() => Some(Self {
|
||||
name: head.into(),
|
||||
style: None,
|
||||
pt_size,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Some((head, tail)) if !head.is_empty() => Some(Self {
|
||||
name: head.into(),
|
||||
style: Some(tail.into()),
|
||||
pt_size: 10.0,
|
||||
}),
|
||||
None if !conf.is_empty() => Some(Self {
|
||||
name: conf.into(),
|
||||
style: None,
|
||||
pt_size: 10.0,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pref_from_multi_name_variant_size() {
|
||||
let pref = FontPreference::from_name_style_size("Noto Serif CJK HK Bold 12").unwrap();
|
||||
assert_eq!(pref.name, "Noto Serif CJK HK");
|
||||
assert_eq!(pref.style, Some("Bold".into()));
|
||||
assert!((pref.pt_size - 12.0).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pref_from_name_variant_size() {
|
||||
let pref = FontPreference::from_name_style_size("Cantarell Bold 12").unwrap();
|
||||
assert_eq!(pref.name, "Cantarell");
|
||||
assert_eq!(pref.style, Some("Bold".into()));
|
||||
assert!((pref.pt_size - 12.0).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pref_from_name_size() {
|
||||
let pref = FontPreference::from_name_style_size("Cantarell 12").unwrap();
|
||||
assert_eq!(pref.name, "Cantarell");
|
||||
assert_eq!(pref.style, None);
|
||||
assert!((pref.pt_size - 12.0).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pref_from_name() {
|
||||
let pref = FontPreference::from_name_style_size("Cantarell").unwrap();
|
||||
assert_eq!(pref.name, "Cantarell");
|
||||
assert_eq!(pref.style, None);
|
||||
assert!((pref.pt_size - 10.0).abs() < f32::EPSILON);
|
||||
}
|
||||
#[test]
|
||||
fn pref_from_multi_name_style() {
|
||||
let pref = FontPreference::from_name_style_size("Foo Bar Baz Bold").unwrap();
|
||||
assert_eq!(pref.name, "Foo Bar Baz");
|
||||
assert_eq!(pref.style, Some("Bold".into()));
|
||||
assert!((pref.pt_size - 10.0).abs() < f32::EPSILON);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue