Add image and hash snapshot-based testing to iced_test

This commit is contained in:
Héctor Ramón Jiménez 2024-12-06 04:06:41 +01:00
parent 8e3636d769
commit 1aeb317f2d
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
17 changed files with 280 additions and 105 deletions

View file

@ -166,11 +166,13 @@ num-traits = "0.2"
once_cell = "1.0"
ouroboros = "0.18"
palette = "0.7"
png = "0.17"
pulldown-cmark = "0.11"
qrcode = { version = "0.13", default-features = false }
raw-window-handle = "0.6"
resvg = "0.42"
rustc-hash = "2.0"
sha2 = "0.10"
smol = "1.0"
smol_str = "0.2"
softbuffer = "0.4"

View file

@ -3,6 +3,8 @@ pub mod palette;
pub use palette::Palette;
use crate::Color;
use std::fmt;
use std::sync::Arc;
@ -246,3 +248,35 @@ impl fmt::Display for Custom {
write!(f, "{}", self.name)
}
}
/// The base style of a [`Theme`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
/// The background [`Color`] of the application.
pub background_color: Color,
/// The default text [`Color`] of the application.
pub text_color: Color,
}
/// The default blank style of a [`Theme`].
pub trait Base {
/// Returns the default base [`Style`] of a [`Theme`].
fn base(&self) -> Style;
}
impl Base for Theme {
fn base(&self) -> Style {
default(self)
}
}
/// The default [`Style`] of a built-in [`Theme`].
pub fn default(theme: &Theme) -> Style {
let palette = theme.extended_palette();
Style {
background_color: palette.background.base.color,
text_color: palette.background.base.text,
}
}

View file

@ -1,5 +1,6 @@
//! Build window-based GUI applications.
pub mod icon;
pub mod screenshot;
pub mod settings;
mod event;
@ -17,5 +18,6 @@ pub use level::Level;
pub use mode::Mode;
pub use position::Position;
pub use redraw_request::RedrawRequest;
pub use screenshot::Screenshot;
pub use settings::Settings;
pub use user_attention::UserAttention;

View file

@ -1,5 +1,5 @@
//! Take screenshots of a window.
use crate::core::{Rectangle, Size};
use crate::{Rectangle, Size};
use bytes::Bytes;
use std::fmt::{Debug, Formatter};

View file

@ -1,5 +1,5 @@
use iced::application;
use iced::gradient;
use iced::theme;
use iced::widget::{
checkbox, column, container, horizontal_space, row, slider, text,
};
@ -95,16 +95,14 @@ impl Gradient {
.into()
}
fn style(&self, theme: &Theme) -> application::Appearance {
use application::DefaultStyle;
fn style(&self, theme: &Theme) -> theme::Style {
if self.transparent {
application::Appearance {
theme::Style {
background_color: Color::TRANSPARENT,
text_color: theme.palette().text,
}
} else {
Theme::default_style(theme)
theme::default(theme)
}
}
}

View file

@ -0,0 +1 @@
5047c10c4ea050393b686185b8d03b3c3738b47182f9892fb68df589ec46bd4b

View file

@ -15,7 +15,7 @@ pub fn main() -> iced::Result {
iced::application(Todos::title, Todos::update, Todos::view)
.subscription(Todos::subscription)
.font(include_bytes!("../fonts/icons.ttf").as_slice())
.font(Todos::ICON_FONT)
.window_size((500.0, 800.0))
.run_with(Todos::new)
}
@ -48,6 +48,8 @@ enum Message {
}
impl Todos {
const ICON_FONT: &[u8] = include_bytes!("../fonts/icons.ttf");
fn new() -> (Self, Command<Message>) {
(
Self::Loading,
@ -449,11 +451,10 @@ fn empty_message(message: &str) -> Element<'_, Message> {
}
// Fonts
const ICONS: Font = Font::with_name("Iced-Todos-Icons");
fn icon(unicode: char) -> Text<'static> {
text(unicode.to_string())
.font(ICONS)
.font(Font::with_name("Iced-Todos-Icons"))
.width(20)
.align_x(Center)
}
@ -594,6 +595,8 @@ mod tests {
#[test]
fn it_creates_a_new_task() -> Result<(), test::Error> {
test::load_font(Todos::ICON_FONT)?;
let (mut todos, _command) = Todos::new();
let _command = todos.update(Message::Loaded(Err(LoadError::File)));
@ -610,6 +613,12 @@ mod tests {
let mut interface = test::interface(todos.view());
let _ = interface.find(selector::text("Create the universe"))?;
let snapshot = interface.snapshot()?;
assert!(
snapshot.matches_hash("snapshots/creates_a_new_task")?,
"snapshots should match!"
);
Ok(())
}
}

View file

@ -1,11 +1,7 @@
//! Build window-based GUI applications.
pub mod screenshot;
pub use screenshot::Screenshot;
use crate::core::time::Instant;
use crate::core::window::{
Event, Icon, Id, Level, Mode, Settings, UserAttention,
Event, Icon, Id, Level, Mode, Screenshot, Settings, UserAttention,
};
use crate::core::{Point, Size};
use crate::futures::event;

View file

@ -31,6 +31,7 @@
//! }
//! ```
use crate::program::{self, Program};
use crate::theme;
use crate::window;
use crate::{
Element, Executor, Font, Result, Settings, Size, Subscription, Task,
@ -38,8 +39,6 @@ use crate::{
use std::borrow::Cow;
pub use crate::shell::program::{Appearance, DefaultStyle};
/// Creates an iced [`Application`] given its title, update, and view logic.
///
/// # Example
@ -76,7 +75,7 @@ pub fn application<State, Message, Theme, Renderer>(
where
State: 'static,
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Theme: Default + theme::Base,
Renderer: program::Renderer,
{
use std::marker::PhantomData;
@ -94,7 +93,7 @@ where
for Instance<State, Message, Theme, Renderer, Update, View>
where
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Theme: Default + theme::Base,
Renderer: program::Renderer,
Update: self::Update<State, Message>,
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
@ -352,7 +351,7 @@ impl<P: Program> Application<P> {
/// Sets the style logic of the [`Application`].
pub fn style(
self,
f: impl Fn(&P::State, &P::Theme) -> Appearance,
f: impl Fn(&P::State, &P::Theme) -> theme::Style,
) -> Application<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> {

View file

@ -1,13 +1,12 @@
//! Create and run daemons that run in the background.
use crate::application;
use crate::program::{self, Program};
use crate::theme;
use crate::window;
use crate::{Element, Executor, Font, Result, Settings, Subscription, Task};
use std::borrow::Cow;
pub use crate::shell::program::{Appearance, DefaultStyle};
/// Creates an iced [`Daemon`] given its title, update, and view logic.
///
/// A [`Daemon`] will not open a window by default, but will run silently
@ -26,7 +25,7 @@ pub fn daemon<State, Message, Theme, Renderer>(
where
State: 'static,
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Theme: Default + theme::Base,
Renderer: program::Renderer,
{
use std::marker::PhantomData;
@ -44,7 +43,7 @@ where
for Instance<State, Message, Theme, Renderer, Update, View>
where
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Theme: Default + theme::Base,
Renderer: program::Renderer,
Update: application::Update<State, Message>,
View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
@ -201,7 +200,7 @@ impl<P: Program> Daemon<P> {
/// Sets the style logic of the [`Daemon`].
pub fn style(
self,
f: impl Fn(&P::State, &P::Theme) -> Appearance,
f: impl Fn(&P::State, &P::Theme) -> theme::Style,
) -> Daemon<
impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
> {

View file

@ -688,7 +688,7 @@ pub fn run<State, Message, Theme, Renderer>(
where
State: Default + 'static,
Message: std::fmt::Debug + Send + 'static,
Theme: Default + program::DefaultStyle + 'static,
Theme: Default + theme::Base + 'static,
Renderer: program::Renderer + 'static,
{
application(title, update, view).run()

View file

@ -1,11 +1,10 @@
use crate::core::text;
use crate::graphics::compositor;
use crate::shell;
use crate::theme;
use crate::window;
use crate::{Element, Executor, Result, Settings, Subscription, Task};
pub use crate::shell::program::{Appearance, DefaultStyle};
/// The internal definition of a [`Program`].
///
/// You should not need to implement this trait directly. Instead, use the
@ -19,7 +18,7 @@ pub trait Program: Sized {
type Message: Send + std::fmt::Debug + 'static;
/// The theme of the program.
type Theme: Default + DefaultStyle;
type Theme: Default + theme::Base;
/// The renderer of the program.
type Renderer: Renderer;
@ -51,11 +50,11 @@ pub trait Program: Sized {
}
fn theme(&self, _state: &Self::State, _window: window::Id) -> Self::Theme {
Self::Theme::default()
<Self::Theme as Default>::default()
}
fn style(&self, _state: &Self::State, theme: &Self::Theme) -> Appearance {
DefaultStyle::default_style(theme)
fn style(&self, _state: &Self::State, theme: &Self::Theme) -> theme::Style {
theme::Base::base(theme)
}
fn scale_factor(&self, _state: &Self::State, _window: window::Id) -> f64 {
@ -153,7 +152,7 @@ pub trait Program: Sized {
self.program.theme(&self.state, window)
}
fn style(&self, theme: &Self::Theme) -> Appearance {
fn style(&self, theme: &Self::Theme) -> theme::Style {
self.program.style(&self.state, theme)
}
@ -252,7 +251,7 @@ pub fn with_title<P: Program>(
&self,
state: &Self::State,
theme: &Self::Theme,
) -> Appearance {
) -> theme::Style {
self.program.style(state, theme)
}
@ -322,7 +321,7 @@ pub fn with_subscription<P: Program>(
&self,
state: &Self::State,
theme: &Self::Theme,
) -> Appearance {
) -> theme::Style {
self.program.style(state, theme)
}
@ -395,7 +394,7 @@ pub fn with_theme<P: Program>(
&self,
state: &Self::State,
theme: &Self::Theme,
) -> Appearance {
) -> theme::Style {
self.program.style(state, theme)
}
@ -409,7 +408,7 @@ pub fn with_theme<P: Program>(
pub fn with_style<P: Program>(
program: P,
f: impl Fn(&P::State, &P::Theme) -> Appearance,
f: impl Fn(&P::State, &P::Theme) -> theme::Style,
) -> impl Program<State = P::State, Message = P::Message, Theme = P::Theme> {
struct WithStyle<P, F> {
program: P,
@ -418,7 +417,7 @@ pub fn with_style<P: Program>(
impl<P: Program, F> Program for WithStyle<P, F>
where
F: Fn(&P::State, &P::Theme) -> Appearance,
F: Fn(&P::State, &P::Theme) -> theme::Style,
{
type State = P::State;
type Message = P::Message;
@ -430,7 +429,7 @@ pub fn with_style<P: Program>(
&self,
state: &Self::State,
theme: &Self::Theme,
) -> Appearance {
) -> theme::Style {
(self.style)(state, theme)
}
@ -535,7 +534,7 @@ pub fn with_scale_factor<P: Program>(
&self,
state: &Self::State,
theme: &Self::Theme,
) -> Appearance {
) -> theme::Style {
self.program.style(state, theme)
}
@ -609,7 +608,7 @@ pub fn with_executor<P: Program, E: Executor>(
&self,
state: &Self::State,
theme: &Self::Theme,
) -> Appearance {
) -> theme::Style {
self.program.style(state, theme)
}

View file

@ -19,3 +19,6 @@ iced_tiny_skia.workspace = true
iced_renderer.workspace = true
iced_renderer.features = ["tiny-skia"]
png.workspace = true
sha2.workspace = true

View file

@ -12,15 +12,26 @@ use iced_tiny_skia as tiny_skia;
use crate::core::clipboard;
use crate::core::keyboard;
use crate::core::mouse;
use crate::core::theme;
use crate::core::time;
use crate::core::widget;
use crate::core::window;
use crate::core::{Element, Event, Font, Pixels, Rectangle, Size, SmolStr};
use crate::renderer::Renderer;
use crate::runtime::user_interface;
use crate::runtime::UserInterface;
use std::borrow::Cow;
use std::fs;
use std::io;
use std::path::Path;
use std::sync::Arc;
pub fn interface<'a, Message, Theme>(
element: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> Interface<'a, Message, Theme, Renderer> {
let size = Size::new(512.0, 512.0);
let mut renderer = Renderer::Secondary(tiny_skia::Renderer::new(
Font::default(),
Pixels(16.0),
@ -28,7 +39,7 @@ pub fn interface<'a, Message, Theme>(
let raw = UserInterface::build(
element,
Size::new(1024.0, 1024.0),
size,
user_interface::Cache::default(),
&mut renderer,
);
@ -36,13 +47,24 @@ pub fn interface<'a, Message, Theme>(
Interface {
raw,
renderer,
size,
messages: Vec::new(),
}
}
pub fn load_font(font: impl Into<Cow<'static, [u8]>>) -> Result<(), Error> {
renderer::graphics::text::font_system()
.write()
.expect("Write to font system")
.load_font(font.into());
Ok(())
}
pub struct Interface<'a, Message, Theme, Renderer> {
raw: UserInterface<'a, Message, Theme, Renderer>,
renderer: Renderer,
size: Size,
messages: Vec<Message>,
}
@ -50,9 +72,9 @@ pub struct Target {
bounds: Rectangle,
}
impl<Message, Theme, Renderer> Interface<'_, Message, Theme, Renderer>
impl<Message, Theme> Interface<'_, Message, Theme, Renderer>
where
Renderer: core::Renderer,
Theme: Default + theme::Base,
{
pub fn find(
&mut self,
@ -256,11 +278,129 @@ where
);
}
pub fn snapshot(&mut self) -> Result<Snapshot, Error> {
let theme = Theme::default();
let base = theme.base();
let _ = self.raw.update(
&[Event::Window(window::Event::RedrawRequested(
time::Instant::now(),
))],
mouse::Cursor::Unavailable,
&mut self.renderer,
&mut clipboard::Null,
&mut self.messages,
);
let _ = self.raw.draw(
&mut self.renderer,
&theme,
&core::renderer::Style {
text_color: base.text_color,
},
mouse::Cursor::Unavailable,
);
if let Renderer::Secondary(renderer) = &mut self.renderer {
let scale_factor = 2.0;
let viewport = renderer::graphics::Viewport::with_physical_size(
Size::new(
(self.size.width * scale_factor).round() as u32,
(self.size.height * scale_factor).round() as u32,
),
f64::from(scale_factor),
);
let rgba = tiny_skia::window::compositor::screenshot::<&str>(
renderer,
&viewport,
base.background_color,
&[],
);
Ok(Snapshot {
screenshot: window::Screenshot::new(
rgba,
viewport.physical_size(),
viewport.scale_factor(),
),
})
} else {
unreachable!()
}
}
pub fn into_messages(self) -> impl IntoIterator<Item = Message> {
self.messages
}
}
pub struct Snapshot {
screenshot: window::Screenshot,
}
impl Snapshot {
pub fn matches_image(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
let path = path.as_ref().with_extension("png");
if path.exists() {
let file = fs::File::open(&path)?;
let decoder = png::Decoder::new(file);
let mut reader = decoder.read_info()?;
let mut bytes = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut bytes)?;
Ok(self.screenshot.bytes == bytes[..info.buffer_size()])
} else {
if let Some(directory) = path.parent() {
fs::create_dir_all(directory)?;
}
let file = fs::File::create(path)?;
let mut encoder = png::Encoder::new(
file,
self.screenshot.size.width,
self.screenshot.size.height,
);
encoder.set_color(png::ColorType::Rgba);
let mut writer = encoder.write_header()?;
writer.write_image_data(&self.screenshot.bytes)?;
writer.finish()?;
Ok(true)
}
}
pub fn matches_hash(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
use sha2::{Digest, Sha256};
let path = path.as_ref().with_extension("sha256");
let hash = {
let mut hasher = Sha256::new();
hasher.update(&self.screenshot.bytes);
format!("{:x}", hasher.finalize())
};
if path.exists() {
let saved_hash = fs::read_to_string(&path)?;
Ok(hash == saved_hash)
} else {
if let Some(directory) = path.parent() {
fs::create_dir_all(directory)?;
}
fs::write(path, hash)?;
Ok(true)
}
}
}
fn key_press_and_release(
key: impl Into<keyboard::Key>,
text: Option<SmolStr>,
@ -293,4 +433,25 @@ fn key_press_and_release(
#[derive(Debug, Clone)]
pub enum Error {
NotFound(Selector),
IOFailed(Arc<io::Error>),
PngDecodingFailed(Arc<png::DecodingError>),
PngEncodingFailed(Arc<png::EncodingError>),
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Self::IOFailed(Arc::new(error))
}
}
impl From<png::DecodingError> for Error {
fn from(error: png::DecodingError) -> Self {
Self::PngDecodingFailed(Arc::new(error))
}
}
impl From<png::EncodingError> for Error {
fn from(error: png::EncodingError) -> Self {
Self::PngEncodingFailed(Arc::new(error))
}
}

View file

@ -8,10 +8,11 @@ use crate::conversion;
use crate::core;
use crate::core::mouse;
use crate::core::renderer;
use crate::core::theme;
use crate::core::time::Instant;
use crate::core::widget::operation;
use crate::core::window;
use crate::core::{Color, Element, Point, Size, Theme};
use crate::core::{Element, Point, Size};
use crate::futures::futures::channel::mpsc;
use crate::futures::futures::channel::oneshot;
use crate::futures::futures::task;
@ -46,7 +47,7 @@ use std::sync::Arc;
pub trait Program
where
Self: Sized,
Self::Theme: DefaultStyle,
Self::Theme: theme::Base,
{
/// The type of __messages__ your [`Program`] will produce.
type Message: std::fmt::Debug + Send;
@ -106,8 +107,8 @@ where
fn theme(&self, window: window::Id) -> Self::Theme;
/// Returns the `Style` variation of the `Theme`.
fn style(&self, theme: &Self::Theme) -> Appearance {
theme.default_style()
fn style(&self, theme: &Self::Theme) -> theme::Style {
theme::Base::base(theme)
}
/// Returns the event `Subscription` for the current state of the
@ -138,37 +139,6 @@ where
}
}
/// The appearance of a program.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Appearance {
/// The background [`Color`] of the application.
pub background_color: Color,
/// The default text [`Color`] of the application.
pub text_color: Color,
}
/// The default style of a [`Program`].
pub trait DefaultStyle {
/// Returns the default style of a [`Program`].
fn default_style(&self) -> Appearance;
}
impl DefaultStyle for Theme {
fn default_style(&self) -> Appearance {
default(self)
}
}
/// The default [`Appearance`] of a [`Program`] with the built-in [`Theme`].
pub fn default(theme: &Theme) -> Appearance {
let palette = theme.extended_palette();
Appearance {
background_color: palette.background.base.color,
text_color: palette.background.base.text,
}
}
/// Runs a [`Program`] with an executor, compositor, and the provided
/// settings.
pub fn run<P, C>(
@ -180,7 +150,7 @@ pub fn run<P, C>(
where
P: Program + 'static,
C: Compositor<Renderer = P::Renderer> + 'static,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
use winit::event_loop::EventLoop;
@ -674,7 +644,7 @@ async fn run_instance<P, C>(
) where
P: Program + 'static,
C: Compositor<Renderer = P::Renderer> + 'static,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
use winit::event;
use winit::event_loop::ControlFlow;
@ -1170,7 +1140,7 @@ fn build_user_interface<'a, P: Program>(
id: window::Id,
) -> UserInterface<'a, P::Message, P::Theme, P::Renderer>
where
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
debug.view_started();
let view = program.view(id);
@ -1189,7 +1159,7 @@ fn update<P: Program, E: Executor>(
debug: &mut Debug,
messages: &mut Vec<P::Message>,
) where
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
for message in messages.drain(..) {
debug.log_message(&message);
@ -1226,7 +1196,7 @@ fn run_action<P, C>(
) where
P: Program,
C: Compositor<Renderer = P::Renderer> + 'static,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
use crate::runtime::clipboard;
use crate::runtime::system;
@ -1461,7 +1431,7 @@ fn run_action<P, C>(
&debug.overlay(),
);
let _ = channel.send(window::Screenshot::new(
let _ = channel.send(core::window::Screenshot::new(
bytes,
window.state.physical_size(),
window.state.viewport().scale_factor(),
@ -1536,7 +1506,7 @@ pub fn build_user_interfaces<'a, P: Program, C>(
) -> FxHashMap<window::Id, UserInterface<'a, P::Message, P::Theme, P::Renderer>>
where
C: Compositor<Renderer = P::Renderer>,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
cached_user_interfaces
.drain()

View file

@ -1,17 +1,18 @@
use crate::conversion;
use crate::core::{mouse, window};
use crate::core::{mouse, theme, window};
use crate::core::{Color, Size};
use crate::graphics::Viewport;
use crate::program::{self, Program};
use std::fmt::{Debug, Formatter};
use crate::program::Program;
use winit::event::{Touch, WindowEvent};
use winit::window::Window;
use std::fmt::{Debug, Formatter};
/// The state of a multi-windowed [`Program`].
pub struct State<P: Program>
where
P::Theme: program::DefaultStyle,
P::Theme: theme::Base,
{
title: String,
scale_factor: f64,
@ -20,12 +21,12 @@ where
cursor_position: Option<winit::dpi::PhysicalPosition<f64>>,
modifiers: winit::keyboard::ModifiersState,
theme: P::Theme,
appearance: program::Appearance,
style: theme::Style,
}
impl<P: Program> Debug for State<P>
where
P::Theme: program::DefaultStyle,
P::Theme: theme::Base,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("multi_window::State")
@ -34,14 +35,14 @@ where
.field("viewport", &self.viewport)
.field("viewport_version", &self.viewport_version)
.field("cursor_position", &self.cursor_position)
.field("appearance", &self.appearance)
.field("style", &self.style)
.finish()
}
}
impl<P: Program> State<P>
where
P::Theme: program::DefaultStyle,
P::Theme: theme::Base,
{
/// Creates a new [`State`] for the provided [`Program`]'s `window`.
pub fn new(
@ -52,7 +53,7 @@ where
let title = application.title(window_id);
let scale_factor = application.scale_factor(window_id);
let theme = application.theme(window_id);
let appearance = application.style(&theme);
let style = application.style(&theme);
let viewport = {
let physical_size = window.inner_size();
@ -71,7 +72,7 @@ where
cursor_position: None,
modifiers: winit::keyboard::ModifiersState::default(),
theme,
appearance,
style,
}
}
@ -127,12 +128,12 @@ where
/// Returns the current background [`Color`] of the [`State`].
pub fn background_color(&self) -> Color {
self.appearance.background_color
self.style.background_color
}
/// Returns the current text [`Color`] of the [`State`].
pub fn text_color(&self) -> Color {
self.appearance.text_color
self.style.text_color
}
/// Processes the provided window event and updates the [`State`] accordingly.
@ -237,6 +238,6 @@ where
// Update theme and appearance
self.theme = application.theme(window_id);
self.appearance = application.style(&self.theme);
self.style = application.style(&self.theme);
}
}

View file

@ -1,9 +1,10 @@
use crate::core::mouse;
use crate::core::theme;
use crate::core::time::Instant;
use crate::core::window::Id;
use crate::core::{Point, Size};
use crate::graphics::Compositor;
use crate::program::{DefaultStyle, Program, State};
use crate::program::{Program, State};
use std::collections::BTreeMap;
use std::sync::Arc;
@ -14,7 +15,7 @@ pub struct WindowManager<P, C>
where
P: Program,
C: Compositor<Renderer = P::Renderer>,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
aliases: BTreeMap<winit::window::WindowId, Id>,
entries: BTreeMap<Id, Window<P, C>>,
@ -24,7 +25,7 @@ impl<P, C> WindowManager<P, C>
where
P: Program,
C: Compositor<Renderer = P::Renderer>,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
pub fn new() -> Self {
Self {
@ -132,7 +133,7 @@ impl<P, C> Default for WindowManager<P, C>
where
P: Program,
C: Compositor<Renderer = P::Renderer>,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
fn default() -> Self {
Self::new()
@ -144,7 +145,7 @@ pub struct Window<P, C>
where
P: Program,
C: Compositor<Renderer = P::Renderer>,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
pub raw: Arc<winit::window::Window>,
pub state: State<P>,
@ -160,7 +161,7 @@ impl<P, C> Window<P, C>
where
P: Program,
C: Compositor<Renderer = P::Renderer>,
P::Theme: DefaultStyle,
P::Theme: theme::Base,
{
pub fn position(&self) -> Option<Point> {
self.raw