#![warn(missing_docs)]
use crate::tree::{El, Rect};
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct UiTarget {
pub key: String,
pub node_id: std::sync::Arc<str>,
pub rect: Rect,
pub tooltip: Option<String>,
pub scroll_offset_y: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PointerButton {
Primary,
Secondary,
Middle,
}
impl PointerButton {
pub const fn from_linux_button(code: u32) -> Option<Self> {
match code {
0x110 => Some(Self::Primary),
0x111 => Some(Self::Secondary),
0x112 => Some(Self::Middle),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PointerKind {
#[default]
Mouse,
Touch,
Pen,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct PointerId(pub u32);
impl PointerId {
pub const PRIMARY: PointerId = PointerId(0);
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Pointer {
pub x: f32,
pub y: f32,
pub button: PointerButton,
pub kind: PointerKind,
pub id: PointerId,
pub pressure: Option<f32>,
}
impl Pointer {
pub fn mouse(x: f32, y: f32, button: PointerButton) -> Self {
Self {
x,
y,
button,
kind: PointerKind::Mouse,
id: PointerId::PRIMARY,
pressure: None,
}
}
pub fn moving(x: f32, y: f32) -> Self {
Self::mouse(x, y, PointerButton::Primary)
}
pub fn touch(x: f32, y: f32, button: PointerButton, id: PointerId) -> Self {
Self {
x,
y,
button,
kind: PointerKind::Touch,
id,
pressure: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LogicalKey {
Named(NamedKey),
Character(String),
Unidentified,
}
impl LogicalKey {
pub fn character(&self) -> Option<&str> {
match self {
LogicalKey::Character(s) => Some(s.as_str()),
_ => None,
}
}
pub fn named(&self) -> Option<NamedKey> {
match self {
LogicalKey::Named(n) => Some(*n),
_ => None,
}
}
}
macro_rules! enum_with_all {
(
$(#[$meta:meta])*
pub enum $name:ident {
$( $(#[$vmeta:meta])* $variant:ident ),+ $(,)?
}
) => {
$(#[$meta])*
pub enum $name {
$( $(#[$vmeta])* $variant ),+
}
impl $name {
pub const ALL: &'static [$name] = &[ $( $name::$variant ),+ ];
}
};
}
pub(crate) use enum_with_all;
enum_with_all! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum NamedKey {
Alt,
AltGraph,
CapsLock,
Control,
Fn,
FnLock,
Meta,
NumLock,
ScrollLock,
Shift,
Super,
Hyper,
Symbol,
Enter,
Tab,
Space,
ArrowDown,
ArrowLeft,
ArrowRight,
ArrowUp,
End,
Home,
PageDown,
PageUp,
Backspace,
Clear,
Copy,
CrSel,
Cut,
Delete,
EraseEof,
ExSel,
Insert,
Paste,
Redo,
Undo,
Accept,
Again,
Cancel,
ContextMenu,
Escape,
Execute,
Find,
Help,
Pause,
Play,
Props,
Select,
ZoomIn,
ZoomOut,
Eject,
Power,
PrintScreen,
WakeUp,
AudioVolumeDown,
AudioVolumeMute,
AudioVolumeUp,
MediaPlayPause,
MediaStop,
MediaTrackNext,
MediaTrackPrevious,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
}
}
enum_with_all! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum PhysicalKey {
Backquote,
Backslash,
BracketLeft,
BracketRight,
Comma,
Digit0,
Digit1,
Digit2,
Digit3,
Digit4,
Digit5,
Digit6,
Digit7,
Digit8,
Digit9,
Equal,
IntlBackslash,
IntlRo,
IntlYen,
KeyA,
KeyB,
KeyC,
KeyD,
KeyE,
KeyF,
KeyG,
KeyH,
KeyI,
KeyJ,
KeyK,
KeyL,
KeyM,
KeyN,
KeyO,
KeyP,
KeyQ,
KeyR,
KeyS,
KeyT,
KeyU,
KeyV,
KeyW,
KeyX,
KeyY,
KeyZ,
Minus,
Period,
Quote,
Semicolon,
Slash,
AltLeft,
AltRight,
Backspace,
CapsLock,
ContextMenu,
ControlLeft,
ControlRight,
Enter,
MetaLeft,
MetaRight,
ShiftLeft,
ShiftRight,
Space,
Tab,
Delete,
End,
Help,
Home,
Insert,
PageDown,
PageUp,
ArrowDown,
ArrowLeft,
ArrowRight,
ArrowUp,
NumLock,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
NumpadAdd,
NumpadBackspace,
NumpadClear,
NumpadComma,
NumpadDecimal,
NumpadDivide,
NumpadEnter,
NumpadEqual,
NumpadMultiply,
NumpadParenLeft,
NumpadParenRight,
NumpadSubtract,
Escape,
PrintScreen,
ScrollLock,
Pause,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
Unidentified,
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct KeyModifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub logo: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct KeyPress {
pub logical: LogicalKey,
pub physical: PhysicalKey,
pub modifiers: KeyModifiers,
pub repeat: bool,
}
impl KeyPress {
pub fn new(
logical: LogicalKey,
physical: PhysicalKey,
modifiers: KeyModifiers,
repeat: bool,
) -> Self {
Self {
logical,
physical,
modifiers,
repeat,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChordTrigger {
Logical(LogicalKey),
Physical(PhysicalKey),
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct KeyChord {
pub trigger: ChordTrigger,
pub modifiers: KeyModifiers,
}
impl KeyChord {
pub fn vim(c: char) -> Self {
Self {
trigger: ChordTrigger::Logical(LogicalKey::Character(c.to_string())),
modifiers: KeyModifiers::default(),
}
}
pub fn ctrl(c: char) -> Self {
Self {
trigger: ChordTrigger::Logical(LogicalKey::Character(c.to_string())),
modifiers: KeyModifiers {
ctrl: true,
..Default::default()
},
}
}
pub fn ctrl_shift(c: char) -> Self {
Self {
trigger: ChordTrigger::Logical(LogicalKey::Character(c.to_string())),
modifiers: KeyModifiers {
ctrl: true,
shift: true,
..Default::default()
},
}
}
pub fn named(key: LogicalKey) -> Self {
Self {
trigger: ChordTrigger::Logical(key),
modifiers: KeyModifiers::default(),
}
}
pub fn physical(key: PhysicalKey) -> Self {
Self {
trigger: ChordTrigger::Physical(key),
modifiers: KeyModifiers::default(),
}
}
pub fn with_modifiers(mut self, modifiers: KeyModifiers) -> Self {
self.modifiers = modifiers;
self
}
pub fn matches(
&self,
logical: &LogicalKey,
physical: PhysicalKey,
modifiers: KeyModifiers,
) -> bool {
self.modifiers == modifiers
&& match &self.trigger {
ChordTrigger::Logical(want) => logical_eq(want, logical),
ChordTrigger::Physical(want) => *want == physical,
}
}
}
fn logical_eq(a: &LogicalKey, b: &LogicalKey) -> bool {
match (a, b) {
(LogicalKey::Character(x), LogicalKey::Character(y)) => x.eq_ignore_ascii_case(y),
_ => a == b,
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct UiEvent {
pub key: Option<String>,
pub target: Option<UiTarget>,
pub pointer: Option<(f32, f32)>,
pub key_press: Option<KeyPress>,
pub text: Option<String>,
pub selection: Option<crate::selection::Selection>,
pub modifiers: KeyModifiers,
pub click_count: u8,
pub path: Option<std::path::PathBuf>,
pub pointer_kind: Option<PointerKind>,
pub wheel_delta: Option<(f32, f32)>,
pub kind: UiEventKind,
}
impl UiEvent {
pub fn synthetic_click(key: impl Into<String>) -> Self {
Self {
kind: UiEventKind::Click,
key: Some(key.into()),
target: None,
pointer: None,
key_press: None,
text: None,
selection: None,
modifiers: KeyModifiers::default(),
click_count: 1,
path: None,
pointer_kind: None,
wheel_delta: None,
}
}
pub fn route(&self) -> Option<&str> {
self.key.as_deref()
}
pub fn target_key(&self) -> Option<&str> {
self.target.as_ref().map(|t| t.key.as_str())
}
pub fn is_route(&self, key: &str) -> bool {
self.route() == Some(key)
}
pub fn route_suffix(&self, prefix: &str) -> Option<&str> {
crate::key::suffix(self.route()?, prefix)
}
pub fn route_index<T: std::str::FromStr>(&self, prefix: &str) -> Option<T> {
crate::key::index(self.route()?, prefix)
}
pub fn is_click_or_activate(&self, key: &str) -> bool {
matches!(self.kind, UiEventKind::Click | UiEventKind::Activate) && self.is_route(key)
}
pub fn is_hotkey(&self, action: &str) -> bool {
self.kind == UiEventKind::Hotkey && self.is_route(action)
}
pub fn pointer_pos(&self) -> Option<(f32, f32)> {
self.pointer
}
pub fn pointer_x(&self) -> Option<f32> {
self.pointer.map(|(x, _)| x)
}
pub fn pointer_y(&self) -> Option<f32> {
self.pointer.map(|(_, y)| y)
}
pub fn wheel_delta(&self) -> Option<(f32, f32)> {
self.wheel_delta
}
pub fn wheel_dy(&self) -> Option<f32> {
self.wheel_delta.map(|(_, dy)| dy)
}
pub fn target_rect(&self) -> Option<Rect> {
self.target.as_ref().map(|t| t.rect)
}
pub fn text(&self) -> Option<&str> {
self.text.as_deref()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum UiEventKind {
Click,
LinkActivated,
SecondaryClick,
MiddleClick,
Activate,
Escape,
Hotkey,
KeyDown,
TextInput,
Drag,
PointerUp,
PointerDown,
PointerWheel,
SelectionChanged,
PointerEnter,
PointerLeave,
PointerCancel,
LongPress,
FileHovered,
FileHoverCancelled,
FileDropped,
Resized,
}
#[derive(Copy, Clone, Debug)]
pub struct BuildCx<'a> {
theme: &'a crate::Theme,
ui_state: Option<&'a crate::state::UiState>,
diagnostics: Option<&'a HostDiagnostics>,
viewport: Option<(f32, f32)>,
safe_area: Option<crate::tree::Sides>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum FrameTrigger {
#[default]
Other,
Initial,
Resize,
Pointer,
Keyboard,
Animation,
ShaderPaint,
Periodic,
External,
}
impl FrameTrigger {
pub fn label(self) -> &'static str {
match self {
FrameTrigger::Other => "other",
FrameTrigger::Initial => "initial",
FrameTrigger::Resize => "resize",
FrameTrigger::Pointer => "pointer",
FrameTrigger::Keyboard => "keyboard",
FrameTrigger::Animation => "animation",
FrameTrigger::ShaderPaint => "shader-paint",
FrameTrigger::Periodic => "periodic",
FrameTrigger::External => "external",
}
}
}
#[derive(Clone, Debug)]
pub struct HostDiagnostics {
pub backend: &'static str,
pub surface_size: (u32, u32),
pub scale_factor: f32,
pub msaa_samples: u32,
pub frame_index: u64,
pub last_frame_dt: std::time::Duration,
pub last_build: std::time::Duration,
pub last_prepare: std::time::Duration,
pub last_layout: std::time::Duration,
pub last_layout_intrinsic_cache_hits: u64,
pub last_layout_intrinsic_cache_misses: u64,
pub last_layout_pruned_subtrees: u64,
pub last_layout_pruned_nodes: u64,
pub last_draw_ops: std::time::Duration,
pub last_draw_ops_culled_text_ops: u64,
pub last_paint: std::time::Duration,
pub last_paint_culled_ops: u64,
pub last_gpu_upload: std::time::Duration,
pub last_snapshot: std::time::Duration,
pub last_submit: std::time::Duration,
pub last_text_layout_cache_hits: u64,
pub last_text_layout_cache_misses: u64,
pub last_text_layout_cache_evictions: u64,
pub last_text_layout_shaped_bytes: u64,
pub trigger: FrameTrigger,
pub working_color_space: crate::color::ColorSpace,
pub color_management: crate::color::ColorManagementStatus,
pub surface_color: Option<SurfaceColorInfo>,
}
impl Default for HostDiagnostics {
fn default() -> Self {
Self {
backend: "?",
surface_size: (0, 0),
scale_factor: 1.0,
msaa_samples: 1,
frame_index: 0,
last_frame_dt: std::time::Duration::ZERO,
last_build: std::time::Duration::ZERO,
last_prepare: std::time::Duration::ZERO,
last_layout: std::time::Duration::ZERO,
last_layout_intrinsic_cache_hits: 0,
last_layout_intrinsic_cache_misses: 0,
last_layout_pruned_subtrees: 0,
last_layout_pruned_nodes: 0,
last_draw_ops: std::time::Duration::ZERO,
last_draw_ops_culled_text_ops: 0,
last_paint: std::time::Duration::ZERO,
last_paint_culled_ops: 0,
last_gpu_upload: std::time::Duration::ZERO,
last_snapshot: std::time::Duration::ZERO,
last_submit: std::time::Duration::ZERO,
last_text_layout_cache_hits: 0,
last_text_layout_cache_misses: 0,
last_text_layout_cache_evictions: 0,
last_text_layout_shaped_bytes: 0,
trigger: FrameTrigger::default(),
working_color_space: crate::paint::DEFAULT_WORKING_COLOR_SPACE,
color_management: crate::color::ColorManagementStatus::default(),
surface_color: None,
}
}
}
impl HostDiagnostics {
pub fn hdr_active(&self) -> bool {
let crate::color::ColorManagementStatus::Available { targets, .. } = &self.color_management
else {
return false;
};
targets.indicates_hdr()
&& self.surface_color.as_ref().is_some_and(|s| {
s.formats
.iter()
.any(|f| f.wide && f.name == s.chosen_format)
})
}
}
#[derive(Clone, Debug, Default)]
pub struct SurfaceColorInfo {
pub adapter: String,
pub driver: String,
pub formats: Vec<SurfaceFormatInfo>,
pub chosen_format: String,
pub present_mode: String,
pub alpha_mode: String,
}
#[derive(Clone, Debug)]
pub struct SurfaceFormatInfo {
pub name: String,
pub srgb: bool,
pub wide: bool,
}
impl<'a> BuildCx<'a> {
pub fn new(theme: &'a crate::Theme) -> Self {
Self {
theme,
ui_state: None,
diagnostics: None,
viewport: None,
safe_area: None,
}
}
pub fn with_ui_state(mut self, ui_state: &'a crate::state::UiState) -> Self {
self.ui_state = Some(ui_state);
self
}
pub fn with_diagnostics(mut self, diagnostics: &'a HostDiagnostics) -> Self {
self.diagnostics = Some(diagnostics);
self
}
pub fn with_viewport(mut self, width: f32, height: f32) -> Self {
self.viewport = Some((width, height));
self
}
pub fn with_safe_area(mut self, sides: crate::tree::Sides) -> Self {
self.safe_area = Some(sides);
self
}
pub fn diagnostics(&self) -> Option<&HostDiagnostics> {
self.diagnostics
}
pub fn theme(&self) -> &crate::Theme {
self.theme
}
pub fn palette(&self) -> &crate::Palette {
self.theme.palette()
}
pub fn viewport(&self) -> Option<(f32, f32)> {
self.viewport
}
pub fn viewport_width(&self) -> Option<f32> {
self.viewport.map(|(w, _)| w)
}
pub fn viewport_height(&self) -> Option<f32> {
self.viewport.map(|(_, h)| h)
}
pub fn viewport_below(&self, threshold: f32) -> bool {
self.viewport_width().is_some_and(|w| w < threshold)
}
pub fn safe_area(&self) -> crate::tree::Sides {
self.safe_area.unwrap_or_default()
}
pub fn safe_area_bottom(&self) -> f32 {
self.safe_area().bottom
}
pub fn hovered_key(&self) -> Option<&str> {
self.ui_state?.hovered_key()
}
pub fn is_hovering_within(&self, key: &str) -> bool {
self.ui_state
.is_some_and(|state| state.is_hovering_within(key))
}
pub fn hovered_scene_point(&self) -> Option<&crate::scene::ScenePointPick> {
self.ui_state?.hovered_scene_point()
}
pub fn rect_of_key(&self, key: &str) -> Option<Rect> {
self.ui_state?.rect_of_key(key)
}
pub fn scene_camera(&self, id: &str) -> Option<crate::scene::CameraState> {
self.ui_state?.scene_camera(id)
}
pub fn visible_range(&self, key: &str) -> Option<std::ops::Range<usize>> {
self.ui_state?.visible_range(key)
}
pub fn viewport_view(&self, key: &str) -> Option<crate::viewport::ViewportView> {
self.ui_state?.viewport_view_by_key(key)
}
pub fn viewport_content_bounds(&self, key: &str) -> Option<Rect> {
self.ui_state?.viewport_content_bounds_by_key(key)
}
pub fn viewport_at_home(&self, key: &str) -> Option<bool> {
self.ui_state?.viewport_at_home_by_key(key)
}
pub fn viewport_in_flight(&self, key: &str) -> Option<bool> {
self.ui_state?.viewport_in_flight_by_key(key)
}
pub fn user_size(&self, key: &str) -> Option<f32> {
self.ui_state?.user_size(key)
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct EventCx<'a> {
ui_state: Option<&'a crate::state::UiState>,
diagnostics: Option<&'a HostDiagnostics>,
viewport: Option<(f32, f32)>,
}
impl<'a> EventCx<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn with_ui_state(mut self, ui_state: &'a crate::state::UiState) -> Self {
self.ui_state = Some(ui_state);
self
}
pub fn with_diagnostics(mut self, diagnostics: &'a HostDiagnostics) -> Self {
self.diagnostics = Some(diagnostics);
self
}
pub fn with_viewport(mut self, width: f32, height: f32) -> Self {
self.viewport = Some((width, height));
self
}
pub fn diagnostics(&self) -> Option<&HostDiagnostics> {
self.diagnostics
}
pub fn viewport(&self) -> Option<(f32, f32)> {
self.viewport
}
pub fn viewport_width(&self) -> Option<f32> {
self.viewport.map(|(w, _)| w)
}
pub fn viewport_height(&self) -> Option<f32> {
self.viewport.map(|(_, h)| h)
}
pub fn viewport_below(&self, threshold: f32) -> bool {
self.viewport_width().is_some_and(|w| w < threshold)
}
pub fn rect_of_key(&self, key: &str) -> Option<Rect> {
self.ui_state?.rect_of_key(key)
}
pub fn scene_camera(&self, id: &str) -> Option<crate::scene::CameraState> {
self.ui_state?.scene_camera(id)
}
pub fn visible_range(&self, key: &str) -> Option<std::ops::Range<usize>> {
self.ui_state?.visible_range(key)
}
pub fn viewport_view(&self, key: &str) -> Option<crate::viewport::ViewportView> {
self.ui_state?.viewport_view_by_key(key)
}
pub fn viewport_content_bounds(&self, key: &str) -> Option<Rect> {
self.ui_state?.viewport_content_bounds_by_key(key)
}
pub fn viewport_at_home(&self, key: &str) -> Option<bool> {
self.ui_state?.viewport_at_home_by_key(key)
}
pub fn viewport_in_flight(&self, key: &str) -> Option<bool> {
self.ui_state?.viewport_in_flight_by_key(key)
}
pub fn user_size(&self, key: &str) -> Option<f32> {
self.ui_state?.user_size(key)
}
}
pub trait App {
fn before_build(&mut self) {}
fn build(&self, cx: &BuildCx) -> El;
fn on_event(&mut self, _event: UiEvent, _cx: &EventCx) {}
fn on_wheel_event(&mut self, event: UiEvent, cx: &EventCx) -> bool {
self.on_event(event, cx);
false
}
fn selection(&self) -> crate::selection::Selection {
crate::selection::Selection::default()
}
fn hotkeys(&self) -> Vec<(KeyChord, String)> {
Vec::new()
}
fn drain_toasts(&mut self) -> Vec<crate::toast::ToastSpec> {
Vec::new()
}
fn drain_focus_requests(&mut self) -> Vec<String> {
Vec::new()
}
fn drain_scroll_requests(&mut self) -> Vec<crate::scroll::ScrollRequest> {
Vec::new()
}
fn drain_viewport_requests(&mut self) -> Vec<crate::viewport::ViewportRequest> {
Vec::new()
}
fn drain_plot_requests(&mut self) -> Vec<crate::plot::PlotRequest> {
Vec::new()
}
fn drain_link_opens(&mut self) -> Vec<String> {
Vec::new()
}
fn shaders(&self) -> Vec<AppShader> {
Vec::new()
}
fn theme(&self) -> crate::Theme {
crate::Theme::default()
}
}
#[derive(Clone, Copy, Debug)]
pub struct AppShader {
pub name: &'static str,
pub wgsl: &'static str,
pub samples_backdrop: bool,
pub samples_time: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pointer_button_from_linux_evdev_codes() {
assert_eq!(
PointerButton::from_linux_button(0x110),
Some(PointerButton::Primary)
);
assert_eq!(
PointerButton::from_linux_button(0x111),
Some(PointerButton::Secondary)
);
assert_eq!(
PointerButton::from_linux_button(0x112),
Some(PointerButton::Middle)
);
assert_eq!(PointerButton::from_linux_button(0x113), None);
assert_eq!(PointerButton::from_linux_button(0), None);
}
use crate::Theme;
#[test]
fn viewport_unset_returns_none_and_breakpoint_returns_false() {
let theme = Theme::default();
let cx = BuildCx::new(&theme);
assert!(cx.viewport().is_none());
assert!(cx.viewport_width().is_none());
assert!(!cx.viewport_below(600.0));
}
#[test]
fn build_cx_surfaces_hovered_scene_point() {
use crate::scene::ScenePointPick;
let theme = Theme::default();
let mut ui = crate::state::UiState::new();
assert!(BuildCx::new(&theme).hovered_scene_point().is_none());
assert!(
BuildCx::new(&theme)
.with_ui_state(&ui)
.hovered_scene_point()
.is_none()
);
ui.set_hovered_scene_point(Some(ScenePointPick {
scene: "scene".into(),
mark: 0,
point: 4,
}));
let cx = BuildCx::new(&theme).with_ui_state(&ui);
let pick = cx.hovered_scene_point().expect("pick surfaced");
assert_eq!(
(pick.scene.as_str(), pick.mark, pick.point),
("scene", 0, 4)
);
}
#[test]
fn viewport_set_exposes_width_and_height() {
let theme = Theme::default();
let cx = BuildCx::new(&theme).with_viewport(420.0, 800.0);
assert_eq!(cx.viewport(), Some((420.0, 800.0)));
assert_eq!(cx.viewport_width(), Some(420.0));
assert_eq!(cx.viewport_height(), Some(800.0));
}
#[test]
fn hdr_active_needs_output_evidence_and_wide_chosen_format() {
use crate::color::{ColorManagementStatus, CompositorColorTargets, TransferFunction};
let hdr_targets = CompositorColorTargets {
preferred_transfer: Some(TransferFunction::Pq),
..Default::default()
};
let scrgb_surface = SurfaceColorInfo {
formats: vec![
SurfaceFormatInfo {
name: "Bgra8UnormSrgb".into(),
srgb: true,
wide: false,
},
SurfaceFormatInfo {
name: "Rgba16Float".into(),
srgb: false,
wide: true,
},
],
chosen_format: "Rgba16Float".into(),
..Default::default()
};
let mut d = HostDiagnostics::default();
assert!(!d.hdr_active());
d.color_management = ColorManagementStatus::Available {
capabilities: Default::default(),
attached: None,
targets: hdr_targets.clone(),
};
d.surface_color = Some(scrgb_surface.clone());
assert!(d.hdr_active());
d.surface_color = Some(SurfaceColorInfo {
chosen_format: "Bgra8UnormSrgb".into(),
..scrgb_surface.clone()
});
assert!(!d.hdr_active());
d.color_management = ColorManagementStatus::Available {
capabilities: Default::default(),
attached: None,
targets: CompositorColorTargets::default(),
};
d.surface_color = Some(scrgb_surface);
assert!(!d.hdr_active());
}
#[test]
fn viewport_below_uses_strict_less_than() {
let theme = Theme::default();
let cx = BuildCx::new(&theme).with_viewport(600.0, 800.0);
assert!(!cx.viewport_below(600.0), "boundary is exclusive");
assert!(cx.viewport_below(601.0));
assert!(!cx.viewport_below(599.0));
}
}