Merge branch 'output' into 'master'
Derive panel size from outputs See merge request World/Phosh/squeekboard!528
This commit is contained in:
		@ -6,12 +6,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use std::time::Duration;
 | 
					use std::time::Duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::outputs::OutputId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// The keyboard should hide after this has elapsed to prevent flickering.
 | 
					/// The keyboard should hide after this has elapsed to prevent flickering.
 | 
				
			||||||
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
 | 
					pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// The outwardly visible state of visibility
 | 
					/// The outwardly visible state of visibility
 | 
				
			||||||
#[derive(PartialEq, Debug, Clone)]
 | 
					#[derive(PartialEq, Debug, Clone)]
 | 
				
			||||||
pub enum Outcome {
 | 
					pub enum Outcome {
 | 
				
			||||||
    Visible,
 | 
					    Visible {
 | 
				
			||||||
 | 
					        output: OutputId,
 | 
				
			||||||
 | 
					        height: u32,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    Hidden,
 | 
					    Hidden,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										358
									
								
								src/assert_matches.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								src/assert_matches.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,358 @@
 | 
				
			|||||||
 | 
					/* Taken from https://github.com/murarth/assert_matches
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * git commit: 26b8b40a12823c068a829ba475d0eccc13dfc221
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * assert_matches is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					Copyright (c) 2016 Murarth
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any
 | 
				
			||||||
 | 
					person obtaining a copy of this software and associated
 | 
				
			||||||
 | 
					documentation files (the "Software"), to deal in the
 | 
				
			||||||
 | 
					Software without restriction, including without
 | 
				
			||||||
 | 
					limitation the rights to use, copy, modify, merge,
 | 
				
			||||||
 | 
					publish, distribute, sublicense, and/or sell copies of
 | 
				
			||||||
 | 
					the Software, and to permit persons to whom the Software
 | 
				
			||||||
 | 
					is furnished to do so, subject to the following
 | 
				
			||||||
 | 
					conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice
 | 
				
			||||||
 | 
					shall be included in all copies or substantial portions
 | 
				
			||||||
 | 
					of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
 | 
				
			||||||
 | 
					ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 | 
				
			||||||
 | 
					TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 | 
				
			||||||
 | 
					PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
 | 
				
			||||||
 | 
					SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 | 
				
			||||||
 | 
					CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 | 
				
			||||||
 | 
					OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 | 
				
			||||||
 | 
					IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 | 
				
			||||||
 | 
					DEALINGS IN THE SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//! Provides a macro, `assert_matches!`, which tests whether a value
 | 
				
			||||||
 | 
					//! matches a given pattern, causing a panic if the match fails.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! See the macro [`assert_matches!`] documentation for more information.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! Also provides a debug-only counterpart, [`debug_assert_matches!`].
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! See the macro [`debug_assert_matches!`] documentation for more information
 | 
				
			||||||
 | 
					//! about this macro.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! [`assert_matches!`]: macro.assert_matches.html
 | 
				
			||||||
 | 
					//! [`debug_assert_matches!`]: macro.debug_assert_matches.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#![deny(missing_docs)]
 | 
				
			||||||
 | 
					#![cfg_attr(not(test), no_std)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Asserts that an expression matches a given pattern.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// A guard expression may be supplied to add further restrictions to the
 | 
				
			||||||
 | 
					/// expected value of the expression.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// A `match` arm may be supplied to perform additional assertions or to yield
 | 
				
			||||||
 | 
					/// a value from the macro invocation.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// # Examples
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					/// #[macro_use] extern crate assert_matches;
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// #[derive(Debug)]
 | 
				
			||||||
 | 
					/// enum Foo {
 | 
				
			||||||
 | 
					///     A(i32),
 | 
				
			||||||
 | 
					///     B(&'static str),
 | 
				
			||||||
 | 
					/// }
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// # fn main() {
 | 
				
			||||||
 | 
					/// let a = Foo::A(1);
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// // Assert that `a` matches the pattern `Foo::A(_)`.
 | 
				
			||||||
 | 
					/// assert_matches!(a, Foo::A(_));
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// // Assert that `a` matches the pattern and
 | 
				
			||||||
 | 
					/// // that the contained value meets the condition `i > 0`.
 | 
				
			||||||
 | 
					/// assert_matches!(a, Foo::A(i) if i > 0);
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// let b = Foo::B("foobar");
 | 
				
			||||||
 | 
					/// 
 | 
				
			||||||
 | 
					/// // Assert that `b` matches the pattern `Foo::B(_)`.
 | 
				
			||||||
 | 
					/// assert_matches!(b, Foo::B(s) => {
 | 
				
			||||||
 | 
					///     // Perform additional assertions on the variable binding `s`.
 | 
				
			||||||
 | 
					///     assert!(s.starts_with("foo"));
 | 
				
			||||||
 | 
					///     assert!(s.ends_with("bar"));
 | 
				
			||||||
 | 
					/// });
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// // Assert that `b` matches the pattern and yield the string `s`.
 | 
				
			||||||
 | 
					/// let s = assert_matches!(b, Foo::B(s) => s);
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// // Perform an assertion on the value `s`.
 | 
				
			||||||
 | 
					/// assert_eq!(s, "foobar");
 | 
				
			||||||
 | 
					/// # }
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					#[macro_export]
 | 
				
			||||||
 | 
					macro_rules! assert_matches {
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ => (),
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ if $cond:expr ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ if $cond => (),
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+ if $cond))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ => $arm:expr ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ => $arm,
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ if $cond => $arm,
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+ if $cond))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ , $($arg:tt)* ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ => (),
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+), format_args!($($arg)*))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ if $cond:expr , $($arg:tt)* ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ if $cond => (),
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+ if $cond), format_args!($($arg)*))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ => $arm:expr , $($arg:tt)* ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ => $arm,
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+), format_args!($($arg)*))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr , $($arg:tt)* ) => {
 | 
				
			||||||
 | 
					        match $e {
 | 
				
			||||||
 | 
					            $($pat)|+ if $cond => $arm,
 | 
				
			||||||
 | 
					            ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
 | 
				
			||||||
 | 
					                e, stringify!($($pat)|+ if $cond), format_args!($($arg)*))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Asserts that an expression matches a given pattern.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only enabled
 | 
				
			||||||
 | 
					/// in non-optimized builds by default. An optimized build will omit all
 | 
				
			||||||
 | 
					/// `debug_assert_matches!` statements unless `-C debug-assertions` is passed
 | 
				
			||||||
 | 
					/// to the compiler.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// See the macro [`assert_matches!`] documentation for more information.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// [`assert_matches!`]: macro.assert_matches.html
 | 
				
			||||||
 | 
					#[macro_export(local_inner_macros)]
 | 
				
			||||||
 | 
					macro_rules! debug_assert_matches {
 | 
				
			||||||
 | 
					    ( $($tt:tt)* ) => { {
 | 
				
			||||||
 | 
					        if _assert_matches_cfg!(debug_assertions) {
 | 
				
			||||||
 | 
					            assert_matches!($($tt)*);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[doc(hidden)]
 | 
				
			||||||
 | 
					#[macro_export]
 | 
				
			||||||
 | 
					macro_rules! _assert_matches_cfg {
 | 
				
			||||||
 | 
					    ( $($tt:tt)* ) => { cfg!($($tt)*) }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod test {
 | 
				
			||||||
 | 
					    use std::panic::{catch_unwind, UnwindSafe};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug)]
 | 
				
			||||||
 | 
					    enum Foo {
 | 
				
			||||||
 | 
					        A(i32),
 | 
				
			||||||
 | 
					        B(&'static str),
 | 
				
			||||||
 | 
					        C(&'static str),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_assert_succeed() {
 | 
				
			||||||
 | 
					        let a = Foo::A(123);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(_));
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(123));
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(i) if i == 123);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(42) | Foo::A(123));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let b = Foo::B("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(_));
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B("foo"));
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) if s == "foo");
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) => assert_eq!(s, "foo"));
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) => { assert_eq!(s, "foo"); assert!(true) });
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "foo"));
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let c = Foo::C("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(c, Foo::B(_) | Foo::C(_));
 | 
				
			||||||
 | 
					        assert_matches!(c, Foo::B("foo") | Foo::C("foo"));
 | 
				
			||||||
 | 
					        assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo");
 | 
				
			||||||
 | 
					        assert_matches!(c, Foo::B(s) | Foo::C(s) => assert_eq!(s, "foo"));
 | 
				
			||||||
 | 
					        assert_matches!(c, Foo::B(s) | Foo::C(s) => { assert_eq!(s, "foo"); assert!(true) });
 | 
				
			||||||
 | 
					        assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => assert_eq!(s, "foo"));
 | 
				
			||||||
 | 
					        assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn test_assert_panic_0() {
 | 
				
			||||||
 | 
					        let a = Foo::A(123);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::B(_));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn test_assert_panic_1() {
 | 
				
			||||||
 | 
					        let b = Foo::B("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B("bar"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn test_assert_panic_2() {
 | 
				
			||||||
 | 
					        let b = Foo::B("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) if s == "bar");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn test_assert_panic_3() {
 | 
				
			||||||
 | 
					        let b = Foo::B("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) => assert_eq!(s, "bar"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn test_assert_panic_4() {
 | 
				
			||||||
 | 
					        let b = Foo::B("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) if s == "bar" => assert_eq!(s, "foo"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn test_assert_panic_5() {
 | 
				
			||||||
 | 
					        let b = Foo::B("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "bar"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    #[should_panic]
 | 
				
			||||||
 | 
					    fn test_assert_panic_6() {
 | 
				
			||||||
 | 
					        let b = Foo::B("foo");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(false) });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_assert_no_move() {
 | 
				
			||||||
 | 
					        let b = &mut Foo::A(0);
 | 
				
			||||||
 | 
					        assert_matches!(*b, Foo::A(0));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn assert_with_message() {
 | 
				
			||||||
 | 
					        let a = Foo::A(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(_), "o noes");
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) if n == 0, "o noes");
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes");
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes");
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes");
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) if n == 0 => { assert_eq!(n, 0); assert!(n < 1) }, "o noes");
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(_), "o noes {:?}", a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) if n == 0, "o noes {:?}", a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {:?}", a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {:?}", a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(_), "o noes {value:?}", value=a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) if n == 0, "o noes {value:?}", value=a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {value:?}", value=a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {value:?}", value=a);
 | 
				
			||||||
 | 
					        assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes {value:?}", value=a);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn panic_message<F>(f: F) -> String
 | 
				
			||||||
 | 
					            where F: FnOnce() + UnwindSafe {
 | 
				
			||||||
 | 
					        let err = catch_unwind(f)
 | 
				
			||||||
 | 
					            .expect_err("function did not panic");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        *err.downcast::<String>()
 | 
				
			||||||
 | 
					            .expect("function panicked with non-String value")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn test_panic_message() {
 | 
				
			||||||
 | 
					        let a = Foo::A(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(_));
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat if cond
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(s) if s == "foo");
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat => arm
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(_) => {});
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat if cond => arm
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(s) if s == "foo" => {});
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat, args
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(_), "msg");
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat if cond, args
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(s) if s == "foo", "msg");
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat => arm, args
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(_) => {}, "msg");
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // expr, pat if cond => arm, args
 | 
				
			||||||
 | 
					        assert_eq!(panic_message(|| {
 | 
				
			||||||
 | 
					            assert_matches!(a, Foo::B(s) if s == "foo" => {}, "msg");
 | 
				
			||||||
 | 
					        }), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -153,6 +153,7 @@ mod test {
 | 
				
			|||||||
    use crate::imservice::{ ContentHint, ContentPurpose };
 | 
					    use crate::imservice::{ ContentHint, ContentPurpose };
 | 
				
			||||||
    use crate::main::PanelCommand;
 | 
					    use crate::main::PanelCommand;
 | 
				
			||||||
    use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
 | 
					    use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
 | 
				
			||||||
 | 
					    use crate::state::test::application_with_fake_output;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn imdetails_new() -> InputMethodDetails {
 | 
					    fn imdetails_new() -> InputMethodDetails {
 | 
				
			||||||
        InputMethodDetails {
 | 
					        InputMethodDetails {
 | 
				
			||||||
@ -170,11 +171,12 @@ mod test {
 | 
				
			|||||||
            im: InputMethod::Active(imdetails_new()),
 | 
					            im: InputMethod::Active(imdetails_new()),
 | 
				
			||||||
            physical_keyboard: Presence::Missing,
 | 
					            physical_keyboard: Presence::Missing,
 | 
				
			||||||
            visibility_override: visibility::State::NotForced,
 | 
					            visibility_override: visibility::State::NotForced,
 | 
				
			||||||
 | 
					            ..application_with_fake_output(start)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let l = State::new(state, now);
 | 
					        let l = State::new(state, now);
 | 
				
			||||||
        let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now);
 | 
					        let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now);
 | 
				
			||||||
        assert_eq!(commands.panel_visibility, Some(PanelCommand::Show));
 | 
					        assert_matches!(commands.panel_visibility, Some(PanelCommand::Show{..}));
 | 
				
			||||||
        assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT));
 | 
					        assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT));
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        now += animation::HIDING_TIMEOUT;
 | 
					        now += animation::HIDING_TIMEOUT;
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,9 @@ extern crate maplit;
 | 
				
			|||||||
extern crate serde;
 | 
					extern crate serde;
 | 
				
			||||||
extern crate xkbcommon;
 | 
					extern crate xkbcommon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					#[macro_use]
 | 
				
			||||||
 | 
					mod assert_matches;
 | 
				
			||||||
#[macro_use]
 | 
					#[macro_use]
 | 
				
			||||||
mod logging;
 | 
					mod logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,6 +40,5 @@ mod style;
 | 
				
			|||||||
mod submission;
 | 
					mod submission;
 | 
				
			||||||
pub mod tests;
 | 
					pub mod tests;
 | 
				
			||||||
pub mod util;
 | 
					pub mod util;
 | 
				
			||||||
mod ui_manager;
 | 
					 | 
				
			||||||
mod vkeyboard;
 | 
					mod vkeyboard;
 | 
				
			||||||
mod xdg;
 | 
					mod xdg;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/main.rs
									
									
									
									
									
								
							@ -3,7 +3,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*! Glue for the main loop. */
 | 
					/*! Glue for the main loop. */
 | 
				
			||||||
 | 
					use crate::outputs::OutputId;
 | 
				
			||||||
use crate::state;
 | 
					use crate::state;
 | 
				
			||||||
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
 | 
					use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,6 +19,7 @@ mod c {
 | 
				
			|||||||
    use crate::imservice::IMService;
 | 
					    use crate::imservice::IMService;
 | 
				
			||||||
    use crate::imservice::c::InputMethod;
 | 
					    use crate::imservice::c::InputMethod;
 | 
				
			||||||
    use crate::outputs::Outputs;
 | 
					    use crate::outputs::Outputs;
 | 
				
			||||||
 | 
					    use crate::outputs::c::WlOutput;
 | 
				
			||||||
    use crate::state;
 | 
					    use crate::state;
 | 
				
			||||||
    use crate::submission::Submission;
 | 
					    use crate::submission::Submission;
 | 
				
			||||||
    use crate::util::c::Wrapped;
 | 
					    use crate::util::c::Wrapped;
 | 
				
			||||||
@ -74,7 +75,7 @@ mod c {
 | 
				
			|||||||
    extern "C" {
 | 
					    extern "C" {
 | 
				
			||||||
        #[allow(improper_ctypes)]
 | 
					        #[allow(improper_ctypes)]
 | 
				
			||||||
        fn init_wayland(wayland: *mut Wayland);
 | 
					        fn init_wayland(wayland: *mut Wayland);
 | 
				
			||||||
        fn server_context_service_real_show_keyboard(service: *const UIManager);
 | 
					        fn server_context_service_update_keyboard(service: *const UIManager, output: WlOutput, height: u32);
 | 
				
			||||||
        fn server_context_service_real_hide_keyboard(service: *const UIManager);
 | 
					        fn server_context_service_real_hide_keyboard(service: *const UIManager);
 | 
				
			||||||
        fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32);
 | 
					        fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32);
 | 
				
			||||||
        // This should probably only get called from the gtk main loop,
 | 
					        // This should probably only get called from the gtk main loop,
 | 
				
			||||||
@ -149,8 +150,8 @@ mod c {
 | 
				
			|||||||
        dbus_handler: *const DBusHandler,
 | 
					        dbus_handler: *const DBusHandler,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        match msg.panel_visibility {
 | 
					        match msg.panel_visibility {
 | 
				
			||||||
            Some(PanelCommand::Show) => unsafe {
 | 
					            Some(PanelCommand::Show { output, height }) => unsafe {
 | 
				
			||||||
                server_context_service_real_show_keyboard(ui_manager);
 | 
					                server_context_service_update_keyboard(ui_manager, output.0, height);
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            Some(PanelCommand::Hide) => unsafe {
 | 
					            Some(PanelCommand::Hide) => unsafe {
 | 
				
			||||||
                server_context_service_real_hide_keyboard(ui_manager);
 | 
					                server_context_service_real_hide_keyboard(ui_manager);
 | 
				
			||||||
@ -178,7 +179,10 @@ mod c {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, PartialEq, Debug)]
 | 
					#[derive(Clone, PartialEq, Debug)]
 | 
				
			||||||
pub enum PanelCommand {
 | 
					pub enum PanelCommand {
 | 
				
			||||||
    Show,
 | 
					    Show {
 | 
				
			||||||
 | 
					        output: OutputId,
 | 
				
			||||||
 | 
					        height: u32,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    Hide,
 | 
					    Hide,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ pub mod c {
 | 
				
			|||||||
    // Defined in C
 | 
					    // Defined in C
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[repr(transparent)]
 | 
					    #[repr(transparent)]
 | 
				
			||||||
    #[derive(Clone, PartialEq, Copy, Debug)]
 | 
					    #[derive(Clone, PartialEq, Copy, Debug, Eq, Hash)]
 | 
				
			||||||
    pub struct WlOutput(*const c_void);
 | 
					    pub struct WlOutput(*const c_void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl WlOutput {
 | 
					    impl WlOutput {
 | 
				
			||||||
@ -118,26 +118,6 @@ pub mod c {
 | 
				
			|||||||
    /// Wrapping Outputs is required for calling its methods from C
 | 
					    /// Wrapping Outputs is required for calling its methods from C
 | 
				
			||||||
    type COutputs = Wrapped<Outputs>;
 | 
					    type COutputs = Wrapped<Outputs>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// A stable reference to an output.
 | 
					 | 
				
			||||||
    #[derive(Clone)]
 | 
					 | 
				
			||||||
    #[repr(C)]
 | 
					 | 
				
			||||||
    pub struct OutputHandle {
 | 
					 | 
				
			||||||
        wl_output: WlOutput,
 | 
					 | 
				
			||||||
        outputs: COutputs,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    impl OutputHandle {
 | 
					 | 
				
			||||||
        // Cannot return an Output reference
 | 
					 | 
				
			||||||
        // because COutputs is too deeply wrapped
 | 
					 | 
				
			||||||
        pub fn get_state(&self) -> Option<OutputState> {
 | 
					 | 
				
			||||||
            let outputs = self.outputs.clone_ref();
 | 
					 | 
				
			||||||
            let outputs = outputs.borrow();
 | 
					 | 
				
			||||||
            outputs
 | 
					 | 
				
			||||||
                .find_output(self.wl_output.clone())
 | 
					 | 
				
			||||||
                .map(|o| o.current.clone())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Defined in Rust
 | 
					    // Defined in Rust
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Callbacks from the output listener follow
 | 
					    // Callbacks from the output listener follow
 | 
				
			||||||
@ -303,17 +283,6 @@ pub mod c {
 | 
				
			|||||||
            .unwrap_or(WlOutput::null())
 | 
					            .unwrap_or(WlOutput::null())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_outputs_get_current(raw_collection: COutputs) -> OutputHandle {
 | 
					 | 
				
			||||||
        let collection = raw_collection.clone_ref();
 | 
					 | 
				
			||||||
        let collection = collection.borrow();
 | 
					 | 
				
			||||||
        OutputHandle {
 | 
					 | 
				
			||||||
            wl_output: collection.outputs[0].0.output.clone(),
 | 
					 | 
				
			||||||
            outputs: raw_collection.clone(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TODO: handle unregistration
 | 
					    // TODO: handle unregistration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -326,15 +295,15 @@ pub struct Size {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// wl_output mode
 | 
					/// wl_output mode
 | 
				
			||||||
#[derive(Clone, Copy, Debug)]
 | 
					#[derive(Clone, Copy, Debug)]
 | 
				
			||||||
struct Mode {
 | 
					pub struct Mode {
 | 
				
			||||||
    width: i32,
 | 
					    width: i32,
 | 
				
			||||||
    height: i32,
 | 
					    height: i32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Copy, Debug)]
 | 
					#[derive(Clone, Copy, Debug)]
 | 
				
			||||||
pub struct OutputState {
 | 
					pub struct OutputState {
 | 
				
			||||||
    current_mode: Option<Mode>,
 | 
					    pub current_mode: Option<Mode>,
 | 
				
			||||||
    transform: Option<c::Transform>,
 | 
					    pub transform: Option<c::Transform>,
 | 
				
			||||||
    pub scale: i32,
 | 
					    pub scale: i32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -382,8 +351,8 @@ impl OutputState {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Not guaranteed to exist,
 | 
					/// Not guaranteed to exist,
 | 
				
			||||||
/// but can be used to look up state.
 | 
					/// but can be used to look up state.
 | 
				
			||||||
#[derive(Clone, Copy, PartialEq, Debug)]
 | 
					#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
 | 
				
			||||||
pub struct OutputId(c::WlOutput);
 | 
					pub struct OutputId(pub c::WlOutput);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WlOutput is a pointer,
 | 
					// WlOutput is a pointer,
 | 
				
			||||||
// but in the public interface,
 | 
					// but in the public interface,
 | 
				
			||||||
@ -399,8 +368,10 @@ struct Output {
 | 
				
			|||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
struct NotFound;
 | 
					struct NotFound;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Wayland global ID type
 | 
				
			||||||
type GlobalId = u32;
 | 
					type GlobalId = u32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The outputs manager
 | 
				
			||||||
pub struct Outputs {
 | 
					pub struct Outputs {
 | 
				
			||||||
    outputs: Vec<(Output, GlobalId)>,
 | 
					    outputs: Vec<(Output, GlobalId)>,
 | 
				
			||||||
    sender: event_loop::driver::Threaded,
 | 
					    sender: event_loop::driver::Threaded,
 | 
				
			||||||
@ -435,14 +406,6 @@ impl Outputs {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn find_output(&self, wl_output: c::WlOutput) -> Option<&Output> {
 | 
					 | 
				
			||||||
        self.outputs
 | 
					 | 
				
			||||||
            .iter()
 | 
					 | 
				
			||||||
            .find_map(|(o, _global)|
 | 
					 | 
				
			||||||
                if o.output == wl_output { Some(o) } else { None }
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn find_output_mut(&mut self, wl_output: c::WlOutput)
 | 
					    fn find_output_mut(&mut self, wl_output: c::WlOutput)
 | 
				
			||||||
        -> Option<&mut Output>
 | 
					        -> Option<&mut Output>
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -463,6 +426,6 @@ pub enum ChangeType {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Copy, Debug)]
 | 
					#[derive(Clone, Copy, Debug)]
 | 
				
			||||||
pub struct Event {
 | 
					pub struct Event {
 | 
				
			||||||
    output: OutputId,
 | 
					    pub output: OutputId,
 | 
				
			||||||
    change: ChangeType,
 | 
					    pub change: ChangeType,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@
 | 
				
			|||||||
#include "submission.h"
 | 
					#include "submission.h"
 | 
				
			||||||
#include "wayland.h"
 | 
					#include "wayland.h"
 | 
				
			||||||
#include "server-context-service.h"
 | 
					#include "server-context-service.h"
 | 
				
			||||||
 | 
					#include "wayland-client-protocol.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum {
 | 
					enum {
 | 
				
			||||||
    PROP_0,
 | 
					    PROP_0,
 | 
				
			||||||
@ -41,11 +42,12 @@ struct _ServerContextService {
 | 
				
			|||||||
    /// Needed for instantiating the widget
 | 
					    /// Needed for instantiating the widget
 | 
				
			||||||
    struct submission *submission; // unowned
 | 
					    struct submission *submission; // unowned
 | 
				
			||||||
    struct squeek_layout_state *layout;
 | 
					    struct squeek_layout_state *layout;
 | 
				
			||||||
    struct ui_manager *manager; // unowned
 | 
					 | 
				
			||||||
    struct squeek_state_manager *state_manager; // shared reference
 | 
					    struct squeek_state_manager *state_manager; // shared reference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    PhoshLayerSurface *window;
 | 
					    PhoshLayerSurface *window;
 | 
				
			||||||
    GtkWidget *widget; // nullable
 | 
					    GtkWidget *widget; // nullable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct wl_output *current_output;
 | 
				
			||||||
    guint last_requested_height;
 | 
					    guint last_requested_height;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -64,96 +66,17 @@ on_destroy (ServerContextService *self, GtkWidget *widget)
 | 
				
			|||||||
    //eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context));
 | 
					    //eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static uint32_t
 | 
					 | 
				
			||||||
calculate_height(int32_t width, GdkRectangle *geometry)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    uint32_t height;
 | 
					 | 
				
			||||||
    if (geometry->width > geometry->height) {
 | 
					 | 
				
			||||||
        // 1:5 ratio works fine on lanscape mode, and makes sure there's
 | 
					 | 
				
			||||||
        // room left for the app window
 | 
					 | 
				
			||||||
        height = width / 5;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        if (width < 540 && width > 0) {
 | 
					 | 
				
			||||||
            height = ((unsigned)width * 7 / 12); // to match 360×210
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            // Here we switch to wide layout, less height needed
 | 
					 | 
				
			||||||
            height = ((unsigned)width * 7 / 22);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return height;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface)
 | 
					make_window (ServerContextService *self, struct wl_output *output, uint32_t height)
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    GdkDisplay *display = NULL;
 | 
					 | 
				
			||||||
    GdkWindow *window = NULL;
 | 
					 | 
				
			||||||
    GdkMonitor *monitor = NULL;
 | 
					 | 
				
			||||||
    GdkRectangle geometry;
 | 
					 | 
				
			||||||
    gint width;
 | 
					 | 
				
			||||||
    gint height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self));
 | 
					 | 
				
			||||||
    g_return_if_fail (PHOSH_IS_LAYER_SURFACE (surface));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    g_object_get(G_OBJECT(surface),
 | 
					 | 
				
			||||||
                 "configured-width", &width,
 | 
					 | 
				
			||||||
                 "configured-height", &height,
 | 
					 | 
				
			||||||
                 NULL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // In order to improve height calculation, we need the monitor geometry so
 | 
					 | 
				
			||||||
    // we can use different algorithms for portrait and landscape mode.
 | 
					 | 
				
			||||||
    // Note: this is a temporary fix until the size manager is complete.
 | 
					 | 
				
			||||||
    display = gdk_display_get_default ();
 | 
					 | 
				
			||||||
    if (display) {
 | 
					 | 
				
			||||||
        window = gtk_widget_get_window (GTK_WIDGET (surface));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (window) {
 | 
					 | 
				
			||||||
        monitor = gdk_display_get_monitor_at_window (display, window);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (monitor) {
 | 
					 | 
				
			||||||
        gdk_monitor_get_geometry (monitor, &geometry);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        geometry.width = geometry.height = 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
     // When the geometry event comes after surface.configure,
 | 
					 | 
				
			||||||
     // this entire height calculation does nothing.
 | 
					 | 
				
			||||||
     // guint desired_height = squeek_uiman_get_perceptual_height(context->manager);
 | 
					 | 
				
			||||||
     // Temporarily use old method, until the size manager is complete.
 | 
					 | 
				
			||||||
    guint desired_height = calculate_height(width, &geometry);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    guint configured_height = (guint)height;
 | 
					 | 
				
			||||||
    // if height was already requested once but a different one was given
 | 
					 | 
				
			||||||
    // (for the same set of surrounding properties),
 | 
					 | 
				
			||||||
    // then it's probably not reasonable to ask for it again,
 | 
					 | 
				
			||||||
    // as it's likely to create pointless loops
 | 
					 | 
				
			||||||
    // of request->reject->request_again->...
 | 
					 | 
				
			||||||
    if (desired_height != configured_height
 | 
					 | 
				
			||||||
            && self->last_requested_height != desired_height) {
 | 
					 | 
				
			||||||
        self->last_requested_height = desired_height;
 | 
					 | 
				
			||||||
        phosh_layer_surface_set_size(surface, 0,
 | 
					 | 
				
			||||||
                                     (gint)desired_height);
 | 
					 | 
				
			||||||
        phosh_layer_surface_set_exclusive_zone(surface, (gint)desired_height);
 | 
					 | 
				
			||||||
        phosh_layer_surface_wl_surface_commit (surface);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
make_window (ServerContextService *self)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (self->window) {
 | 
					    if (self->window) {
 | 
				
			||||||
        g_error("Window already present");
 | 
					        g_error("Window already present");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct squeek_output_handle output = squeek_outputs_get_current(squeek_wayland->outputs);
 | 
					 | 
				
			||||||
    squeek_uiman_set_output(self->manager, output);
 | 
					 | 
				
			||||||
    uint32_t height = squeek_uiman_get_perceptual_height(self->manager);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    self->window = g_object_new (
 | 
					    self->window = g_object_new (
 | 
				
			||||||
        PHOSH_TYPE_LAYER_SURFACE,
 | 
					        PHOSH_TYPE_LAYER_SURFACE,
 | 
				
			||||||
        "layer-shell", squeek_wayland->layer_shell,
 | 
					        "layer-shell", squeek_wayland->layer_shell,
 | 
				
			||||||
        "wl-output", output.output,
 | 
					        "wl-output", output,
 | 
				
			||||||
        "height", height,
 | 
					        "height", height,
 | 
				
			||||||
        "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
 | 
					        "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
 | 
				
			||||||
                  | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
 | 
					                  | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
 | 
				
			||||||
@ -167,7 +90,7 @@ make_window (ServerContextService *self)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    g_object_connect (self->window,
 | 
					    g_object_connect (self->window,
 | 
				
			||||||
                      "swapped-signal::destroy", G_CALLBACK(on_destroy), self,
 | 
					                      "swapped-signal::destroy", G_CALLBACK(on_destroy), self,
 | 
				
			||||||
                      "swapped-signal::configured", G_CALLBACK(on_surface_configure), self,
 | 
					                      //"swapped-signal::configured", G_CALLBACK(on_surface_configure), self,
 | 
				
			||||||
                      NULL);
 | 
					                      NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The properties below are just to make hacking easier.
 | 
					    // The properties below are just to make hacking easier.
 | 
				
			||||||
@ -204,11 +127,49 @@ make_widget (ServerContextService *self)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Called from rust
 | 
					// Called from rust
 | 
				
			||||||
 | 
					/// Updates the type of hiddenness
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
server_context_service_real_show_keyboard (ServerContextService *self)
 | 
					server_context_service_real_hide_keyboard (ServerContextService *self)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    //self->desired_height = 0;
 | 
				
			||||||
 | 
					    self->current_output = NULL;
 | 
				
			||||||
 | 
					    if (self->window) {
 | 
				
			||||||
 | 
					        gtk_widget_hide (GTK_WIDGET(self->window));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called from rust
 | 
				
			||||||
 | 
					/// Updates the type of visibility
 | 
				
			||||||
 | 
					void
 | 
				
			||||||
 | 
					server_context_service_update_keyboard (ServerContextService *self, struct wl_output *output, uint32_t height)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (output != self->current_output) {
 | 
				
			||||||
 | 
					        // Recreate on a new output
 | 
				
			||||||
 | 
					        server_context_service_real_hide_keyboard(self);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        gint h;
 | 
				
			||||||
 | 
					        PhoshLayerSurface *surface = self->window;
 | 
				
			||||||
 | 
					        g_object_get(G_OBJECT(surface),
 | 
				
			||||||
 | 
					                     "configured-height", &h,
 | 
				
			||||||
 | 
					                     NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ((uint32_t)h != height) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //TODO: make sure that redrawing happens in the correct place (it doesn't now).
 | 
				
			||||||
 | 
					            phosh_layer_surface_set_size(self->window, 0, height);
 | 
				
			||||||
 | 
					            phosh_layer_surface_set_exclusive_zone(self->window, height);
 | 
				
			||||||
 | 
					            phosh_layer_surface_wl_surface_commit(self->window);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self->current_output = output;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self->current_output = output;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!self->window) {
 | 
					    if (!self->window) {
 | 
				
			||||||
        make_window (self);
 | 
					        make_window (self, output, height);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!self->widget) {
 | 
					    if (!self->widget) {
 | 
				
			||||||
        make_widget (self);
 | 
					        make_widget (self);
 | 
				
			||||||
@ -216,14 +177,6 @@ server_context_service_real_show_keyboard (ServerContextService *self)
 | 
				
			|||||||
    gtk_widget_show (GTK_WIDGET(self->window));
 | 
					    gtk_widget_show (GTK_WIDGET(self->window));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Called from rust
 | 
					 | 
				
			||||||
void
 | 
					 | 
				
			||||||
server_context_service_real_hide_keyboard (ServerContextService *self)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    if (self->window) {
 | 
					 | 
				
			||||||
	    gtk_widget_hide (GTK_WIDGET(self->window));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
server_context_service_set_property (GObject      *object,
 | 
					server_context_service_set_property (GObject      *object,
 | 
				
			||||||
@ -318,13 +271,12 @@ init (ServerContextService *self) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ServerContextService *
 | 
					ServerContextService *
 | 
				
			||||||
server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman,  struct squeek_state_manager *state_manager)
 | 
					server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
 | 
					    ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
 | 
				
			||||||
    ui->submission = submission;
 | 
					    ui->submission = submission;
 | 
				
			||||||
    ui->state = self;
 | 
					    ui->state = self;
 | 
				
			||||||
    ui->layout = layout;
 | 
					    ui->layout = layout;
 | 
				
			||||||
    ui->manager = uiman;
 | 
					 | 
				
			||||||
    ui->state_manager = state_manager;
 | 
					    ui->state_manager = state_manager;
 | 
				
			||||||
    init(ui);
 | 
					    init(ui);
 | 
				
			||||||
    return ui;
 | 
					    return ui;
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "src/layout.h"
 | 
					#include "src/layout.h"
 | 
				
			||||||
#include "src/submission.h"
 | 
					#include "src/submission.h"
 | 
				
			||||||
#include "ui_manager.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
G_BEGIN_DECLS
 | 
					G_BEGIN_DECLS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,7 +28,7 @@ G_BEGIN_DECLS
 | 
				
			|||||||
/** Manages the lifecycle of the window displaying layouts. */
 | 
					/** Manages the lifecycle of the window displaying layouts. */
 | 
				
			||||||
G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONTEXT_SERVICE, GObject)
 | 
					G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONTEXT_SERVICE, GObject)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct squeek_state_manager *state_manager);
 | 
					ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager);
 | 
				
			||||||
enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
 | 
					enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
 | 
				
			||||||
G_END_DECLS
 | 
					G_END_DECLS
 | 
				
			||||||
#endif  /* SERVER_CONTEXT_SERVICE_H */
 | 
					#endif  /* SERVER_CONTEXT_SERVICE_H */
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,6 @@
 | 
				
			|||||||
#include "outputs.h"
 | 
					#include "outputs.h"
 | 
				
			||||||
#include "submission.h"
 | 
					#include "submission.h"
 | 
				
			||||||
#include "server-context-service.h"
 | 
					#include "server-context-service.h"
 | 
				
			||||||
#include "ui_manager.h"
 | 
					 | 
				
			||||||
#include "wayland.h"
 | 
					#include "wayland.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <gdk/gdkwayland.h>
 | 
					#include <gdk/gdkwayland.h>
 | 
				
			||||||
@ -56,8 +55,6 @@ struct squeekboard {
 | 
				
			|||||||
    ServerContextService *ui_context; // mess, includes the entire UI
 | 
					    ServerContextService *ui_context; // mess, includes the entire UI
 | 
				
			||||||
    /// Currently wanted layout. TODO: merge into state::Application
 | 
					    /// Currently wanted layout. TODO: merge into state::Application
 | 
				
			||||||
    struct squeek_layout_state layout_choice;
 | 
					    struct squeek_layout_state layout_choice;
 | 
				
			||||||
    /// UI shape tracker/chooser. TODO: merge into state::Application
 | 
					 | 
				
			||||||
    struct ui_manager *ui_manager;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -400,8 +397,6 @@ main (int argc, char **argv)
 | 
				
			|||||||
    // Also initializes wayland
 | 
					    // Also initializes wayland
 | 
				
			||||||
    struct rsobjects rsobjects = squeek_init();
 | 
					    struct rsobjects rsobjects = squeek_init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    instance.ui_manager = squeek_uiman_new();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    instance.settings_context = eekboard_context_service_new(&instance.layout_choice);
 | 
					    instance.settings_context = eekboard_context_service_new(&instance.layout_choice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // set up dbus
 | 
					    // set up dbus
 | 
				
			||||||
@ -446,7 +441,6 @@ main (int argc, char **argv)
 | 
				
			|||||||
                instance.settings_context,
 | 
					                instance.settings_context,
 | 
				
			||||||
                rsobjects.submission,
 | 
					                rsobjects.submission,
 | 
				
			||||||
                &instance.layout_choice,
 | 
					                &instance.layout_choice,
 | 
				
			||||||
                instance.ui_manager,
 | 
					 | 
				
			||||||
                rsobjects.state_manager);
 | 
					                rsobjects.state_manager);
 | 
				
			||||||
    if (!ui_context) {
 | 
					    if (!ui_context) {
 | 
				
			||||||
        g_error("Could not initialize GUI");
 | 
					        g_error("Could not initialize GUI");
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										146
									
								
								src/state.rs
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								src/state.rs
									
									
									
									
									
								
							@ -9,9 +9,11 @@ use crate::animation;
 | 
				
			|||||||
use crate::imservice::{ ContentHint, ContentPurpose };
 | 
					use crate::imservice::{ ContentHint, ContentPurpose };
 | 
				
			||||||
use crate::main::{ Commands, PanelCommand };
 | 
					use crate::main::{ Commands, PanelCommand };
 | 
				
			||||||
use crate::outputs;
 | 
					use crate::outputs;
 | 
				
			||||||
 | 
					use crate::outputs::{OutputId, OutputState};
 | 
				
			||||||
 | 
					use std::cmp;
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::time::Instant;
 | 
					use std::time::Instant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Clone, Copy)]
 | 
					#[derive(Clone, Copy)]
 | 
				
			||||||
pub enum Presence {
 | 
					pub enum Presence {
 | 
				
			||||||
    Present,
 | 
					    Present,
 | 
				
			||||||
@ -92,12 +94,12 @@ impl Outcome {
 | 
				
			|||||||
    pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
 | 
					    pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
 | 
				
			||||||
        let layout_hint_set = match new_state {
 | 
					        let layout_hint_set = match new_state {
 | 
				
			||||||
            Outcome {
 | 
					            Outcome {
 | 
				
			||||||
                visibility: animation::Outcome::Visible,
 | 
					                visibility: animation::Outcome::Visible{..},
 | 
				
			||||||
                im: InputMethod::Active(hints),
 | 
					                im: InputMethod::Active(hints),
 | 
				
			||||||
            } => Some(hints.clone()),
 | 
					            } => Some(hints.clone()),
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            Outcome {
 | 
					            Outcome {
 | 
				
			||||||
                visibility: animation::Outcome::Visible,
 | 
					                visibility: animation::Outcome::Visible{..},
 | 
				
			||||||
                im: InputMethod::InactiveSince(_),
 | 
					                im: InputMethod::InactiveSince(_),
 | 
				
			||||||
            } => Some(InputMethodDetails {
 | 
					            } => Some(InputMethodDetails {
 | 
				
			||||||
                hint: ContentHint::NONE,
 | 
					                hint: ContentHint::NONE,
 | 
				
			||||||
@ -109,9 +111,10 @@ impl Outcome {
 | 
				
			|||||||
                ..
 | 
					                ..
 | 
				
			||||||
            } => None,
 | 
					            } => None,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					// FIXME: handle switching outputs
 | 
				
			||||||
        let (dbus_visible_set, panel_visibility) = match new_state.visibility {
 | 
					        let (dbus_visible_set, panel_visibility) = match new_state.visibility {
 | 
				
			||||||
            animation::Outcome::Visible => (Some(true), Some(PanelCommand::Show)),
 | 
					            animation::Outcome::Visible{output, height}
 | 
				
			||||||
 | 
					                => (Some(true), Some(PanelCommand::Show{output, height})),
 | 
				
			||||||
            animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)),
 | 
					            animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -139,6 +142,13 @@ pub struct Application {
 | 
				
			|||||||
    pub im: InputMethod,
 | 
					    pub im: InputMethod,
 | 
				
			||||||
    pub visibility_override: visibility::State,
 | 
					    pub visibility_override: visibility::State,
 | 
				
			||||||
    pub physical_keyboard: Presence,
 | 
					    pub physical_keyboard: Presence,
 | 
				
			||||||
 | 
					    /// The output on which the panel should appear.
 | 
				
			||||||
 | 
					    /// This is stored as part of the state
 | 
				
			||||||
 | 
					    /// because it's not clear how to derive the output from the rest of the state.
 | 
				
			||||||
 | 
					    /// It should probably follow the focused input,
 | 
				
			||||||
 | 
					    /// but not sure about being allowed on non-touch displays.
 | 
				
			||||||
 | 
					    pub preferred_output: Option<OutputId>,
 | 
				
			||||||
 | 
					    pub outputs: HashMap<OutputId, OutputState>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Application {
 | 
					impl Application {
 | 
				
			||||||
@ -153,6 +163,8 @@ impl Application {
 | 
				
			|||||||
            im: InputMethod::InactiveSince(now),
 | 
					            im: InputMethod::InactiveSince(now),
 | 
				
			||||||
            visibility_override: visibility::State::NotForced,
 | 
					            visibility_override: visibility::State::NotForced,
 | 
				
			||||||
            physical_keyboard: Presence::Missing,
 | 
					            physical_keyboard: Presence::Missing,
 | 
				
			||||||
 | 
					            preferred_output: None,
 | 
				
			||||||
 | 
					            outputs: Default::default(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -173,9 +185,23 @@ impl Application {
 | 
				
			|||||||
                ..self
 | 
					                ..self
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Event::Output(output) => {
 | 
					            Event::Output(outputs::Event { output, change }) => {
 | 
				
			||||||
                println!("Stub: output event {:?}", output);
 | 
					                let mut app = self;
 | 
				
			||||||
                self
 | 
					                match change {
 | 
				
			||||||
 | 
					                    outputs::ChangeType::Altered(state) => {
 | 
				
			||||||
 | 
					                        app.outputs.insert(output, state);
 | 
				
			||||||
 | 
					                        app.preferred_output = app.preferred_output.or(Some(output));
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    outputs::ChangeType::Removed => {
 | 
				
			||||||
 | 
					                        app.outputs.remove(&output);
 | 
				
			||||||
 | 
					                        if app.preferred_output == Some(output) {
 | 
				
			||||||
 | 
					                            // There's currently no policy to choose one output over another,
 | 
				
			||||||
 | 
					                            // so just take whichever comes first.
 | 
				
			||||||
 | 
					                            app.preferred_output = app.outputs.keys().next().map(|output| *output);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                app
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Event::InputMethod(new_im) => match (self.im.clone(), new_im) {
 | 
					            Event::InputMethod(new_im) => match (self.im.clone(), new_im) {
 | 
				
			||||||
@ -212,20 +238,51 @@ impl Application {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_preferred_height(output: &OutputState) -> Option<u32> {
 | 
				
			||||||
 | 
					        output.get_pixel_size()
 | 
				
			||||||
 | 
					            .map(|px_size| {
 | 
				
			||||||
 | 
					                let height = {
 | 
				
			||||||
 | 
					                    if px_size.width > px_size.height {
 | 
				
			||||||
 | 
					                        px_size.width / 5
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if (px_size.width < 540) & (px_size.width > 0) {
 | 
				
			||||||
 | 
					                            px_size.width * 7 / 12 // to match 360×210
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            // Here we switch to wide layout, less height needed
 | 
				
			||||||
 | 
					                            px_size.width * 7 / 22
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                cmp::min(height, px_size.height / 2)
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn get_outcome(&self, now: Instant) -> Outcome {
 | 
					    pub fn get_outcome(&self, now: Instant) -> Outcome {
 | 
				
			||||||
        // FIXME: include physical keyboard presence
 | 
					        // FIXME: include physical keyboard presence
 | 
				
			||||||
        Outcome {
 | 
					        Outcome {
 | 
				
			||||||
            visibility: match (self.physical_keyboard, self.visibility_override) {
 | 
					            visibility: match self.preferred_output {
 | 
				
			||||||
                (_, visibility::State::ForcedHidden) => animation::Outcome::Hidden,
 | 
					                None => animation::Outcome::Hidden,
 | 
				
			||||||
                (_, visibility::State::ForcedVisible) => animation::Outcome::Visible,
 | 
					                Some(output) => {
 | 
				
			||||||
                (Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden,
 | 
					                    // Hoping that this will get optimized out on branches not using `visible`.
 | 
				
			||||||
                (Presence::Missing, visibility::State::NotForced) => match self.im {
 | 
					                    let height = Self::get_preferred_height(self.outputs.get(&output).unwrap())
 | 
				
			||||||
                    InputMethod::Active(_) => animation::Outcome::Visible,
 | 
					                        .unwrap_or(0);
 | 
				
			||||||
                    InputMethod::InactiveSince(since) => {
 | 
					                    // TODO: Instead of setting size to 0 when the output is invalid,
 | 
				
			||||||
                        if now < since + animation::HIDING_TIMEOUT { animation::Outcome::Visible }
 | 
					                    // simply go invisible.
 | 
				
			||||||
                        else { animation::Outcome::Hidden }
 | 
					                    let visible = animation::Outcome::Visible{output, height};
 | 
				
			||||||
                    },
 | 
					                    
 | 
				
			||||||
                },
 | 
					                    match (self.physical_keyboard, self.visibility_override) {
 | 
				
			||||||
 | 
					                        (_, visibility::State::ForcedHidden) => animation::Outcome::Hidden,
 | 
				
			||||||
 | 
					                        (_, visibility::State::ForcedVisible) => visible,
 | 
				
			||||||
 | 
					                        (Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden,
 | 
				
			||||||
 | 
					                        (Presence::Missing, visibility::State::NotForced) => match self.im {
 | 
				
			||||||
 | 
					                            InputMethod::Active(_) => visible,
 | 
				
			||||||
 | 
					                            InputMethod::InactiveSince(since) => {
 | 
				
			||||||
 | 
					                                if now < since + animation::HIDING_TIMEOUT { visible }
 | 
				
			||||||
 | 
					                                else { animation::Outcome::Hidden }
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            im: self.im.clone(),
 | 
					            im: self.im.clone(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -250,9 +307,9 @@ impl Application {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod test {
 | 
					pub mod test {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
					    use crate::outputs::c::WlOutput;
 | 
				
			||||||
    use std::time::Duration;
 | 
					    use std::time::Duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn imdetails_new() -> InputMethodDetails {
 | 
					    fn imdetails_new() -> InputMethodDetails {
 | 
				
			||||||
@ -262,6 +319,30 @@ mod test {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn fake_output_id(id: usize) -> OutputId {
 | 
				
			||||||
 | 
					        OutputId(unsafe {
 | 
				
			||||||
 | 
					            std::mem::transmute::<_, WlOutput>(id)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn application_with_fake_output(start: Instant) -> Application {
 | 
				
			||||||
 | 
					        let id = fake_output_id(1);
 | 
				
			||||||
 | 
					        let mut outputs = HashMap::new();
 | 
				
			||||||
 | 
					        outputs.insert(
 | 
				
			||||||
 | 
					            id,
 | 
				
			||||||
 | 
					            OutputState {
 | 
				
			||||||
 | 
					                current_mode: None,
 | 
				
			||||||
 | 
					                transform: None,
 | 
				
			||||||
 | 
					                scale: 1,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        Application {
 | 
				
			||||||
 | 
					            preferred_output: Some(id),
 | 
				
			||||||
 | 
					            outputs,
 | 
				
			||||||
 | 
					            ..Application::new(start)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Test the original delay scenario: no flicker on quick switches.
 | 
					    /// Test the original delay scenario: no flicker on quick switches.
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn avoid_hide() {
 | 
					    fn avoid_hide() {
 | 
				
			||||||
@ -271,15 +352,16 @@ mod test {
 | 
				
			|||||||
            im: InputMethod::Active(imdetails_new()),
 | 
					            im: InputMethod::Active(imdetails_new()),
 | 
				
			||||||
            physical_keyboard: Presence::Missing,
 | 
					            physical_keyboard: Presence::Missing,
 | 
				
			||||||
            visibility_override: visibility::State::NotForced,
 | 
					            visibility_override: visibility::State::NotForced,
 | 
				
			||||||
 | 
					            ..application_with_fake_output(start)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
 | 
					        let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
 | 
				
			||||||
        // Check 100ms at 1ms intervals. It should remain visible.
 | 
					        // Check 100ms at 1ms intervals. It should remain visible.
 | 
				
			||||||
        for _i in 0..100 {
 | 
					        for _i in 0..100 {
 | 
				
			||||||
            now += Duration::from_millis(1);
 | 
					            now += Duration::from_millis(1);
 | 
				
			||||||
            assert_eq!(
 | 
					            assert_matches!(
 | 
				
			||||||
                state.get_outcome(now).visibility,
 | 
					                state.get_outcome(now).visibility,
 | 
				
			||||||
                animation::Outcome::Visible,
 | 
					                animation::Outcome::Visible{..},
 | 
				
			||||||
                "Hidden when it should remain visible: {:?}",
 | 
					                "Hidden when it should remain visible: {:?}",
 | 
				
			||||||
                now.saturating_duration_since(start),
 | 
					                now.saturating_duration_since(start),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@ -287,7 +369,7 @@ mod test {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
 | 
					        let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(state.get_outcome(now).visibility, animation::Outcome::Visible);
 | 
					        assert_matches!(state.get_outcome(now).visibility, animation::Outcome::Visible{..});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Make sure that hiding works when input method goes away
 | 
					    /// Make sure that hiding works when input method goes away
 | 
				
			||||||
@ -299,11 +381,12 @@ mod test {
 | 
				
			|||||||
            im: InputMethod::Active(imdetails_new()),
 | 
					            im: InputMethod::Active(imdetails_new()),
 | 
				
			||||||
            physical_keyboard: Presence::Missing,
 | 
					            physical_keyboard: Presence::Missing,
 | 
				
			||||||
            visibility_override: visibility::State::NotForced,
 | 
					            visibility_override: visibility::State::NotForced,
 | 
				
			||||||
 | 
					            ..application_with_fake_output(start)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
 | 
					        let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while let animation::Outcome::Visible = state.get_outcome(now).visibility {
 | 
					        while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
 | 
				
			||||||
            now += Duration::from_millis(1);
 | 
					            now += Duration::from_millis(1);
 | 
				
			||||||
            assert!(
 | 
					            assert!(
 | 
				
			||||||
                now < start + Duration::from_millis(250),
 | 
					                now < start + Duration::from_millis(250),
 | 
				
			||||||
@ -323,6 +406,7 @@ mod test {
 | 
				
			|||||||
            im: InputMethod::Active(imdetails_new()),
 | 
					            im: InputMethod::Active(imdetails_new()),
 | 
				
			||||||
            physical_keyboard: Presence::Missing,
 | 
					            physical_keyboard: Presence::Missing,
 | 
				
			||||||
            visibility_override: visibility::State::NotForced,
 | 
					            visibility_override: visibility::State::NotForced,
 | 
				
			||||||
 | 
					            ..application_with_fake_output(start)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        // This reflects the sequence from Wayland:
 | 
					        // This reflects the sequence from Wayland:
 | 
				
			||||||
        // disable, disable, enable, disable
 | 
					        // disable, disable, enable, disable
 | 
				
			||||||
@ -332,7 +416,7 @@ mod test {
 | 
				
			|||||||
        let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
 | 
					        let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
 | 
				
			||||||
        let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
 | 
					        let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while let animation::Outcome::Visible = state.get_outcome(now).visibility {
 | 
					        while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
 | 
				
			||||||
            now += Duration::from_millis(1);
 | 
					            now += Duration::from_millis(1);
 | 
				
			||||||
            assert!(
 | 
					            assert!(
 | 
				
			||||||
                now < start + Duration::from_millis(250),
 | 
					                now < start + Duration::from_millis(250),
 | 
				
			||||||
@ -361,13 +445,14 @@ mod test {
 | 
				
			|||||||
            im: InputMethod::InactiveSince(now),
 | 
					            im: InputMethod::InactiveSince(now),
 | 
				
			||||||
            physical_keyboard: Presence::Missing,
 | 
					            physical_keyboard: Presence::Missing,
 | 
				
			||||||
            visibility_override: visibility::State::NotForced,
 | 
					            visibility_override: visibility::State::NotForced,
 | 
				
			||||||
 | 
					            ..application_with_fake_output(start)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        now += Duration::from_secs(1);
 | 
					        now += Duration::from_secs(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
 | 
					        let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_matches!(
 | 
				
			||||||
            state.get_outcome(now).visibility,
 | 
					            state.get_outcome(now).visibility,
 | 
				
			||||||
            animation::Outcome::Visible,
 | 
					            animation::Outcome::Visible{..},
 | 
				
			||||||
            "Failed to show: {:?}",
 | 
					            "Failed to show: {:?}",
 | 
				
			||||||
            now.saturating_duration_since(start),
 | 
					            now.saturating_duration_since(start),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -394,6 +479,7 @@ mod test {
 | 
				
			|||||||
            im: InputMethod::Active(imdetails_new()),
 | 
					            im: InputMethod::Active(imdetails_new()),
 | 
				
			||||||
            physical_keyboard: Presence::Missing,
 | 
					            physical_keyboard: Presence::Missing,
 | 
				
			||||||
            visibility_override: visibility::State::NotForced,
 | 
					            visibility_override: visibility::State::NotForced,
 | 
				
			||||||
 | 
					            ..application_with_fake_output(start)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        now += Duration::from_secs(1);
 | 
					        now += Duration::from_secs(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -420,9 +506,9 @@ mod test {
 | 
				
			|||||||
        now += Duration::from_secs(1);
 | 
					        now += Duration::from_secs(1);
 | 
				
			||||||
        let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
 | 
					        let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert_eq!(
 | 
					        assert_matches!(
 | 
				
			||||||
            state.get_outcome(now).visibility,
 | 
					            state.get_outcome(now).visibility,
 | 
				
			||||||
            animation::Outcome::Visible,
 | 
					            animation::Outcome::Visible{..},
 | 
				
			||||||
            "Failed to appear: {:?}",
 | 
					            "Failed to appear: {:?}",
 | 
				
			||||||
            now.saturating_duration_since(start),
 | 
					            now.saturating_duration_since(start),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@
 | 
				
			|||||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
 | 
					#include "virtual-keyboard-unstable-v1-client-protocol.h"
 | 
				
			||||||
#include "eek/eek-types.h"
 | 
					#include "eek/eek-types.h"
 | 
				
			||||||
#include "main.h"
 | 
					#include "main.h"
 | 
				
			||||||
#include "src/ui_manager.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct squeek_layout;
 | 
					struct squeek_layout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,19 +0,0 @@
 | 
				
			|||||||
#ifndef UI_MANAGER__
 | 
					 | 
				
			||||||
#define UI_MANAGER__
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <inttypes.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "eek/eek-types.h"
 | 
					 | 
				
			||||||
#include "outputs.h"
 | 
					 | 
				
			||||||
#include "main.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct ui_manager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct ui_manager *squeek_uiman_new(void);
 | 
					 | 
				
			||||||
void squeek_uiman_set_output(struct ui_manager *uiman, struct squeek_output_handle output);
 | 
					 | 
				
			||||||
uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct vis_manager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct vis_manager *squeek_visman_new(struct squeek_state_manager *state_manager);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
@ -1,82 +0,0 @@
 | 
				
			|||||||
/* Copyright (C) 2020, 2021 Purism SPC
 | 
					 | 
				
			||||||
 * SPDX-License-Identifier: GPL-3.0+
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*! Centrally manages the shape of the UI widgets, and the choice of layout.
 | 
					 | 
				
			||||||
 * 
 | 
					 | 
				
			||||||
 * Coordinates this based on information collated from all possible sources.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::cmp::min;
 | 
					 | 
				
			||||||
use ::outputs::c::OutputHandle;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub mod c {
 | 
					 | 
				
			||||||
    use super::*;
 | 
					 | 
				
			||||||
    use ::util::c::Wrapped;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_uiman_new() -> Wrapped<Manager> {
 | 
					 | 
				
			||||||
        Wrapped::new(Manager { output: None })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Used to size the layer surface containing all the OSK widgets.
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_uiman_get_perceptual_height(
 | 
					 | 
				
			||||||
        uiman: Wrapped<Manager>,
 | 
					 | 
				
			||||||
    ) -> u32 {
 | 
					 | 
				
			||||||
        let uiman = uiman.clone_ref();
 | 
					 | 
				
			||||||
        let uiman = uiman.borrow();
 | 
					 | 
				
			||||||
        // TODO: what to do when there's no output?
 | 
					 | 
				
			||||||
        uiman.get_perceptual_height().unwrap_or(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[no_mangle]
 | 
					 | 
				
			||||||
    pub extern "C"
 | 
					 | 
				
			||||||
    fn squeek_uiman_set_output(
 | 
					 | 
				
			||||||
        uiman: Wrapped<Manager>,
 | 
					 | 
				
			||||||
        output: OutputHandle,
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        let uiman = uiman.clone_ref();
 | 
					 | 
				
			||||||
        let mut uiman = uiman.borrow_mut();
 | 
					 | 
				
			||||||
        uiman.output = Some(output);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Stores current state of all things influencing what the UI should look like.
 | 
					 | 
				
			||||||
pub struct Manager {
 | 
					 | 
				
			||||||
    /// Shared output handle, current state updated whenever it's needed.
 | 
					 | 
				
			||||||
    // TODO: Stop assuming that the output never changes.
 | 
					 | 
				
			||||||
    // (There's no way for the output manager to update the ui manager.)
 | 
					 | 
				
			||||||
    // FIXME: Turn into an OutputState and apply relevant connections elsewhere.
 | 
					 | 
				
			||||||
    // Otherwise testability and predictablity is low.
 | 
					 | 
				
			||||||
    output: Option<OutputHandle>,
 | 
					 | 
				
			||||||
    //// Pixel size of the surface. Needs explicit updating.
 | 
					 | 
				
			||||||
    //surface_size: Option<Size>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Manager {
 | 
					 | 
				
			||||||
    fn get_perceptual_height(&self) -> Option<u32> {
 | 
					 | 
				
			||||||
        let output_info = (&self.output).as_ref()
 | 
					 | 
				
			||||||
            .and_then(|o| o.get_state())
 | 
					 | 
				
			||||||
            .map(|os| (os.scale as u32, os.get_pixel_size()));
 | 
					 | 
				
			||||||
        match output_info {
 | 
					 | 
				
			||||||
            Some((scale, Some(px_size))) => Some({
 | 
					 | 
				
			||||||
                let height = if (px_size.width < 720) & (px_size.width > 0) {
 | 
					 | 
				
			||||||
                    px_size.width * 7 / 12 // to match 360×210
 | 
					 | 
				
			||||||
                } else if px_size.width < 1080 {
 | 
					 | 
				
			||||||
                    360 + (1080 - px_size.width) * 60 / 360 // smooth transition
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    360
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Don't exceed half the display size
 | 
					 | 
				
			||||||
                min(height, px_size.height / 2) / scale
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
            Some((scale, None)) => Some(360 / scale),
 | 
					 | 
				
			||||||
            None => None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user