Repository: fltk-rs/fltk-rs (language: rust)

Issue:
A Screen type
Hi, first of all thank you for all the good work you're doing here. I'm loving how light, portable, versatile, customizable, stable, and simple to use FLTK seems to be, and I'm very happy that it really looks totally usable from Rust, idiomatically and without sugar/dark-magic. Your examples and videos are also helping a lot to figure out things quicker so thank you very much for that.


When looking on how to get screen information I was surprised to found only a [thinly wrapped subset](https://docs.rs/fltk/latest/src/fltk/app/screen.rs.html) of the [fltk-1.4 screen functions](https://www.fltk.org/doc-1.4/group__fl__screen.html).

I don't know if you have considered it before, but it maybe nice to have a `Screen` abstraction (like `Window` and `App`), and have the `screen_` funtions under its namespace.

Exposing the missing overloaded functions of `screen_num`, `screen_work_area` and `screen_xywh` as differently suffixed methods would be nice too, like for example: 

```rust
// Gets the screen number for the screen which intersects the most with the rectangle defined by x, y, w, h. 
fn screen_num_intersecting(x: i32, y: i32, w: i32, h: i32) // or screen_num_most_inside?...
```

Another step would be to allow instantation of a `Screen`, wrapping a screen number, and then making the relevant methods can `&self` so that they can be used both as instance's methods and also as uninstanced associated functions.

Does this make sense to you in the context of the vision of the API you have?


Discussion / hints:
Hi
Thank you. 
Much of the screen functions (like most non-widget related code) were added on a per need basis. I wouldn’t mind adding a Screen abstraction and porting the rest of the screen functions. The old screen functions would need to be kept as well for api compatibility. 
Good, I'll try to create a PR then.

BTW I see the function `screen_size`() returns the non-exported `screen_w` and `screen_h` functions but casted as `f64`. Was there a specific reason for that?
The old implementation used to divide or multiply (can’t remember) the returned values by 0.96. 
The problem was my dev pc, and that wasn’t really required since it was a scaling issue. 

Thanks, understood.

I have another question, regarding returning bounding boxes for the work area, instead of returning a tuple of 4 unnamed values (x,y,w,h) ideally I think it would be more user friendly and versatile to return something like a `struct BoundingBox { x:i32, y:i32, w:i32, h:i32 }`.

The only problem I see is global coherence with the rest of the API, since there are 21 other functions places returning a tuple of four `i32`s (although I'm not sure if in every case its `x,y,w,h` or in some cases may be `x1,y1,x2,y2`. Most of those are public traits functions so it would be a breaking change. 

What is your opinion on this? Do you like the idea of return a bounding box, or a rect, for the upcoming version 2.0?

I would prefer Rect over BoundingBox. 

Generally returns of a tuple of four i32 vals is x, y, w, h. 

Theres a Coords struct which can also be used especially to refactor the drawing methods, prior to a 2.0 version. 



> Theres a Coords struct which can also be used especially to refactor the drawing methods, prior to a 2.0 version.

Ok, I can make a new generic `Rect` struct under the `draw` module, too, in the style of `Coord`.

1. I see there are several cases like `draw_point(i32, i32)` alongside `draw_point2(Coord<i32>)`. Are those created in preparation for 2.0? Will they be unified/deprecated?

2. In that case do you see the creation of a `measure2(...) -> Coord` function, `wrap_measure2(...)`, `text_extents2(...) -> Rect<i32>`, etc.? 

3. I see some naming inconsistencies like `draw_arc()` which takes `i32` and `draw_arc2()` which takes `f64`. Maybe cases like that should be renamed for 2.0 adding a more semantic suffix?


Another thing, regarding the ergonomics of using the generic `Coord` and future `Rect`, in order to avoid entering `Coord<i32>` everywhere, maybe some type aliases could be created to make `Coord` more convenient to use in the most common case? Some like this: 
```rust
pub struct Coordinates<T: Copy>(pub T, pub T); //rename the generic
pub type Coord = Coordinates<i32>; // the most common case the briefest to write
pub type Coord_f64 = Coordinantes<f64>;

// the more fields there are, the most important is they're named
pub struct Rectangle<T: Copy>{
  pub x: T,
  pub y: T,
  pub w: T,
  pub h: T,
}
pub type Rect = Rectangle<i32>;
pub type Rect_f64 = Rectangle<f64>;
```

Finally, do you have a strong preference regarding using unnamed tuple fields for `Coord` instead of named `x, y` struct fields?
Yeah it can be generic along with aliasing for convenience. Coords can also be tuple struct with unnamed fields. 

Fix patch:
diff --git a/fltk-sys/src/fl.rs b/fltk-sys/src/fl.rs
index 10a3aba7..b1cd7903 100644
--- a/fltk-sys/src/fl.rs
+++ b/fltk-sys/src/fl.rs
@@ -451,6 +451,12 @@ extern "C" {
         x: ::std::os::raw::c_int,
         y: ::std::os::raw::c_int,
     ) -> ::std::os::raw::c_int;
+    pub fn Fl_screen_num_inside(
+        x: ::std::os::raw::c_int,
+        y: ::std::os::raw::c_int,
+        w: ::std::os::raw::c_int,
+        h: ::std::os::raw::c_int,
+    ) -> ::std::os::raw::c_int;
 }
 extern "C" {
     pub fn Fl_screen_xywh(
@@ -460,6 +466,30 @@ extern "C" {
         H: *mut ::std::os::raw::c_int,
         n: ::std::os::raw::c_int,
     );
+    pub fn Fl_screen_xywh_at(
+        X: *mut ::std::os::raw::c_int,
+        Y: *mut ::std::os::raw::c_int,
+        W: *mut ::std::os::raw::c_int,
+        H: *mut ::std::os::raw::c_int,
+        mx: ::std::os::raw::c_int,
+        my: ::std::os::raw::c_int,
+    );
+    pub fn Fl_screen_xywh_inside(
+        X: *mut ::std::os::raw::c_int,
+        Y: *mut ::std::os::raw::c_int,
+        W: *mut ::std::os::raw::c_int,
+        H: *mut ::std::os::raw::c_int,
+        mx: ::std::os::raw::c_int,
+        my: ::std::os::raw::c_int,
+        mw: ::std::os::raw::c_int,
+        mh: ::std::os::raw::c_int,
+    );
+    pub fn Fl_screen_xywh_mouse(
+        X: *mut ::std::os::raw::c_int,
+        Y: *mut ::std::os::raw::c_int,
+        W: *mut ::std::os::raw::c_int,
+        H: *mut ::std::os::raw::c_int,
+    );
 }
 extern "C" {
     pub fn Fl_screen_dpi(h: *mut f32, v: *mut f32, n: ::std::os::raw::c_int);
@@ -472,6 +502,25 @@ extern "C" {
         H: *mut ::std::os::raw::c_int,
         n: ::std::os::raw::c_int,
     );
+    pub fn Fl_screen_work_area_mouse(
+        X: *mut ::std::os::raw::c_int,
+        Y: *mut ::std::os::raw::c_int,
+        W: *mut ::std::os::raw::c_int,
+        H: *mut ::std::os::raw::c_int,
+    );
+    pub fn Fl_screen_work_area_at(
+        X: *mut ::std::os::raw::c_int,
+        Y: *mut ::std::os::raw::c_int,
+        W: *mut ::std::os::raw::c_int,
+        H: *mut ::std::os::raw::c_int,
+        mx: ::std::os::raw::c_int,
+        my: ::std::os::raw::c_int,
+    );
+}
+extern "C" {
+    pub fn Fl_keyboard_screen_scaling(
+        value: ::std::os::raw::c_int,
+    );
 }
 extern "C" {
     pub fn Fl_open_display();
diff --git a/fltk/examples/screens_info.rs b/fltk/examples/screens_info.rs
new file mode 100644
index 00000000..4dd34fc3
--- /dev/null
+++ b/fltk/examples/screens_info.rs
@@ -0,0 +1,42 @@
+use fltk::{
+    app::Screen,
+    draw::{Coord, Rect},
+};
+
+fn main() {
+    let screens = Screen::all_screens();
+    println!("Number of detected screens = {}\n", screens.len());
+    assert_eq![screens.len(), Screen::count() as usize];
+
+    println!("Is scaling supported:");
+    println!("- at all = {}", Screen::scaling_supported());
+    println!("- separately = {}", Screen::scaling_supported_separately());
+
+    let coord: Coord = [100, 100].into();
+    let rect: Rect = [100, 100, 100, 100].into();
+
+    // uncomment these lines to see out-of-boundaries errors:
+    // let coord: Coord = [-100, 10_000].into();
+    // let rect: Rect = [-100, 100, 10_000, 10_000].into();
+
+    println!("\nScreen found:");
+    println!("- at {coord:?} = {:?}", Screen::new_at(coord));
+    println!("- inside {rect:?} = {:?}", Screen::new_inside(rect));
+
+    println!("\nWork area:");
+    println!("- at {coord:?} = {:?}", Screen::work_area_at(coord));
+    println!("- under the mouse = {:?}", Screen::work_area_mouse());
+
+    println!("\nXYWH:");
+    println!("- at {coord:?} = {:?}", Screen::xywh_at(coord));
+    println!("- inside {rect:?} = {:?}", Screen::xywh_inside(rect));
+    println!("- under the mouse = {:?}", Screen::xywh_mouse());
+
+    println!("\nFor each screen:");
+    for s in screens {
+        println!("+ {s:?}");
+        println!("  - work area = {:?}:", s.work_area());
+        println!("  - dpi = {:?}", s.dpi());
+        println!("  - scale = {:?}", s.scale());
+    }
+}
diff --git a/fltk/src/app/event.rs b/fltk/src/app/event.rs
index b51cdf95..485d7d07 100644
--- a/fltk/src/app/event.rs
+++ b/fltk/src/app/event.rs
@@ -118,7 +118,7 @@ pub enum MouseWheel {
 }
 
 /// Returns the current horizontal mouse scrolling associated with the Mousewheel event.
-/// Returns [`MouseWheel::None`](`crate::enums::MouseWheel::None`), `Right` or `Left`
+/// Returns [`MouseWheel::None`], `Right` or `Left`
 pub fn event_dx() -> MouseWheel {
     match 0.cmp(unsafe { &fl::Fl_event_dx() }) {
         cmp::Ordering::Greater => MouseWheel::Right,
@@ -128,7 +128,7 @@ pub fn event_dx() -> MouseWheel {
 }
 
 /// Returns the current horizontal mouse scrolling associated with the Mousewheel event.
-/// Returns [`MouseWheel::None`](`crate::enums::MouseWheel::None`), `Up` or `Down`.
+/// Returns [`MouseWheel::None`], `Up` or `Down`.
 /// Doesn't indicate scrolling direction which depends on system preferences
 pub fn event_dy() -> MouseWheel {
     match 0.cmp(unsafe { &fl::Fl_event_dy() }) {
@@ -309,11 +309,11 @@ pub unsafe fn handle_raw(event: Event, w: WindowPtr) -> bool {
     The event dispatch function is called after native events are converted to
     FLTK events, but before they are handled by FLTK. If the dispatch function
     handler is set, it is up to the dispatch function to call
-    [`app::handle2(Event, WindowPtr)`](crate::app::handle2) or to ignore the event.
+    [`app::handle(Event, WindowPtr)`](crate::app::handle) or to ignore the event.
 
     The dispatch function itself must return false if it ignored the event,
-    or true if it used the event. If you call [`app::handle2()`](crate::app::handle2), then
-    this will return the correct value.
+    or true if it used the event. If you call [`app::handle()`](crate::app::handle),
+    then this will return the correct value.
     # Safety
     The window pointer must not be invalidated
 */
diff --git a/fltk/src/app/screen.rs b/fltk/src/app/screen.rs
index 65d01017..9fbd02f0 100644
--- a/fltk/src/app/screen.rs
+++ b/fltk/src/app/screen.rs
@@ -1,5 +1,329 @@
+// https://www.fltk.org/doc-1.4/group__fl__screen.html
+
 use fltk_sys::fl;
 
+use crate::{
+    draw::{Coordinates, Rect},
+    prelude::{FltkError, FltkErrorKind},
+};
+type Coord = Coordinates<i32>; // TEMP
+
+/// An available screen
+///
+/// Unlike the standalone functions, it automatically checks the provided
+/// coordinates and screen numbers are currently valid and inside boundaries.
+#[derive(Debug, Copy, Clone)]
+pub struct Screen {
+    /// The screen number
+    pub n: i32,
+}
+
+impl Screen {
+    // constructors
+
+    /// Returns a vector containing all the `Screen`s, ordered by screen number
+    pub fn all_screens() -> Vec<Screen> {
+        let mut screens: Vec<Self> = vec![];
+        for n in 0..Self::count() {
+            screens.push(Self::new(n).unwrap());
+        }
+        screens
+    }
+
+    /// Returns the `Screen` associated with the given screen number
+    ///
+    /// Returns an error if the provided `number` is not a valid screen number.
+    pub fn new(number: i32) -> Result<Screen, FltkError> {
+        let s = Screen { n: number };
+        if s.is_valid() {
+            Ok(s)
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Returns the `Screen` that contains the specified screen position
+    ///
+    /// Returns an error if the provided coordinates are out of bounds.
+    pub fn new_at<C: Into<Coord> + Copy>(pos: C) -> Result<Screen, FltkError> {
+        let pos: Coord = pos.into();
+
+        let s = Screen {
+            n: unsafe { fl::Fl_screen_num(pos.x, pos.y) },
+        };
+
+        if Self::is_coord_inside_any_work_area(pos) {
+            Ok(s)
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Returns the `Screen` which intersects the most with the provided rectangle
+    ///
+    /// Returns an error if any coordinates on the provided retangle are out of bounds.
+    pub fn new_inside<R: Into<Rect> + Copy>(rect: R) -> Result<Screen, FltkError> {
+        let r: Rect = rect.into();
+        let s = Screen {
+            n: unsafe { fl::Fl_screen_num_inside(r.x, r.y, r.w, r.h) },
+        };
+
+        if Self::is_rect_inside_any_work_area(r) {
+            Ok(s)
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    // associated functions
+
+    /// Returns true if scaling factors are supported by this platform,
+    /// wether shared by all screens, or each screen having its own value
+    pub fn scaling_supported() -> bool {
+        unsafe { fl::Fl_screen_scaling_supported() != 0 }
+    }
+
+    /// Returns true if each screen can have its own scaling factor value
+    pub fn scaling_supported_separately() -> bool {
+        unsafe { fl::Fl_screen_scaling_supported() == 2 }
+    }
+
+    /// Returns the number of available screens
+    pub fn count() -> i32 {
+        unsafe { fl::Fl_screen_count() }
+    }
+
+    /// Returns the screen number of the screen that contains
+    /// the specified screen position coordinates
+    ///
+    /// Returns an error if the provided coordinates are out of bounds.
+    pub fn num_at<C: Into<Coord> + Copy>(pos: C) -> Result<i32, FltkError> {
+        let pos: Coord = pos.into();
+        if Self::is_coord_inside_any_work_area(pos) {
+            Ok(unsafe { fl::Fl_screen_num(pos.x, pos.y) })
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Returns the bounding rectangle of the work area of the screen that
+    /// contains the specified screen position coordinates
+    ///
+    /// Returns an error if the provided coordinates are out of bounds.
+    pub fn work_area_at<C: Into<Coord> + Copy>(pos: C) -> Result<Rect, FltkError> {
+        let pos: Coord = pos.into();
+        if Self::is_coord_inside_any_work_area(pos) {
+            let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+            unsafe { fl::Fl_screen_work_area_at(&mut x, &mut y, &mut w, &mut h, pos.x, pos.y) }
+            Ok(Rect { x, y, w, h })
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Returns the bounding rectangle of the work area of the screen currently under
+    /// the mouse pointer coordinates
+    pub fn work_area_mouse() -> Rect {
+        let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+        unsafe { fl::Fl_screen_work_area_mouse(&mut x, &mut y, &mut w, &mut h) }
+        Rect { x, y, w, h }
+    }
+
+    /// Returns the bounding rectangle of the work area
+    /// with the provided screen `number`
+    ///
+    /// Returns an error if the provided `number` is not a valid screen number.
+    pub fn work_area_num(number: i32) -> Result<Rect, FltkError> {
+        let s = Screen { n: number };
+        if s.is_valid() {
+            let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+            unsafe { fl::Fl_screen_work_area(&mut x, &mut y, &mut w, &mut h, number) }
+            Ok(Rect { x, y, w, h })
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Returns the bounding rectangle of the screen that
+    /// contains the specified screen position coordinates
+    ///
+    /// Returns an error if the provided coordinates are out of bounds.
+    pub fn xywh_at<C: Into<Coord> + Copy>(pos: C) -> Result<Rect, FltkError> {
+        let pos: Coord = pos.into();
+        if Self::is_coord_inside_any_xywh(pos) {
+            let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+            unsafe { fl::Fl_screen_xywh_at(&mut x, &mut y, &mut w, &mut h, pos.x, pos.y) }
+            Ok(Rect { x, y, w, h })
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Returns the bounding rectangle of the screen that
+    /// contains the specified screen position coordinates
+    ///
+    /// Returns an error if the provided coordinates are out of bounds.
+    pub fn xywh_inside<R: Into<Rect> + Copy>(rect: R) -> Result<Rect, FltkError> {
+        let r: Rect = rect.into();
+        if Self::is_rect_inside_any_xywh(r) {
+            let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+            unsafe { fl::Fl_screen_xywh_inside(&mut x, &mut y, &mut w, &mut h, r.x, r.y, r.w, r.h) }
+            Ok(Rect { x, y, w, h })
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Gets the bounding rectangle of the screen currently under
+    /// the mouse pointer coordinates
+    pub fn xywh_mouse() -> Rect {
+        let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+        unsafe { fl::Fl_screen_xywh_mouse(&mut x, &mut y, &mut w, &mut h) }
+        Rect { x, y, w, h }
+    }
+
+    /// Returns the bounding rectangle of the screen
+    /// with the provided screen `number`
+    ///
+    /// Returns an error if the provided `number` is not a valid screen number.
+    pub fn xywh_num(number: i32) -> Result<Rect, FltkError> {
+        let s = Screen { n: number };
+        if s.is_valid() {
+            let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+            unsafe {
+                fl::Fl_screen_xywh(&mut x, &mut y, &mut w, &mut h, number);
+            }
+            Ok(Rect { x, y, w, h })
+        } else {
+            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
+        }
+    }
+
+    /// Controls the possibility to scale all windows by ctrl/+/-/0/ or cmd/+/-/0/
+    ///
+    /// This function should be called before `app::open_display` runs.
+    /// If it is not called, the default is to handle these keys for window scaling.
+    ///
+    /// Pass a `value` of `0` to stop recognition of ctrl/+/-/0/
+    /// (or cmd/+/-/0/ under macOS) keys as window scaling.
+    ///
+    /// # Panics
+    /// Panics if `value` != 0.
+    pub fn keyboard_scaling(value: i32) {
+        assert_eq![0, value];
+        unsafe { fl::Fl_keyboard_screen_scaling(value) }
+    }
+
+    // methods
+
+    /// Returns `true` if the current screen's number corresponds
+    /// to a currently connected screen, or `false` otherwise.
+    pub fn is_valid(&self) -> bool {
+        self.n >= 0 && self.n < Self::count()
+    }
+
+    /// Returns the current screen (vertical, horizontal) resolution in dots-per-inch
+    pub fn dpi(&self) -> (f32, f32) {
+        let mut h: f32 = 0.;
+        let mut v: f32 = 0.;
+        unsafe {
+            fl::Fl_screen_dpi(&mut h, &mut v, self.n);
+        }
+        (h, v)
+    }
+
+    /// Sets the value of the GUI scaling factor for the current screen
+    pub fn set_scale(&self, factor: f32) {
+        unsafe { fl::Fl_set_screen_scale(self.n, factor) }
+    }
+
+    /// Returns the value of the GUI scaling factor for the current screen
+    pub fn scale(&self) -> f32 {
+        unsafe { fl::Fl_screen_scale(self.n) }
+    }
+
+    /// Returns the the work area of the current screen
+    pub fn work_area(&self) -> Rect {
+        let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
+        unsafe { fl::Fl_screen_work_area(&mut x, &mut y, &mut w, &mut h, self.n) }
+        Rect { x, y, w, h }
+    }
+
+    /// Returns the topmost y coordinate of the current screen's work area
+    pub fn y(&self) -> i32 {
+        self.work_area().y
+    }
+
+    /// Returns the bottom-right x coordinate of the current screen's work area
+    pub fn x(&self) -> i32 {
+        self.work_area().x
+    }
+
+    /// Returns the width in pixels of the current screen's work area
+    pub fn w(&self) -> i32 {
+        self.work_area().w
+    }
+
+    /// Returns the height in pixels of the current screen's work area
+    pub fn h(&self) -> i32 {
+        self.work_area().h
+    }
+
+    /// Returns the top-left `x,y` coordinates of the current screen's work area
+    pub fn top_left(&self) -> Coord {
+        self.work_area().top_left()
+    }
+
+    /// Returns the bottom-right `x+w, y+h` coordinates of the current screen's work area
+    pub fn bottom_right(&self) -> Coord {
+        self.work_area().bottom_right()
+    }
+
+    // private methods
+
+    // returns `true` if the provided position is inside the bounds
+    // of any current work area boundaries, or `false` otherwise.
+    fn is_coord_inside_any_work_area<C: Into<Coord> + Copy>(c: C) -> bool {
+        let c: Coord = c.into();
+        let main_wa: Rect = screen_work_area(0).into();
+        // returns false if we get `0` but the coords are outside main screen's
+        !(screen_num(c.x, c.y) == 0
+            && (c.x < main_wa.x
+                || c.y < main_wa.y
+                || c.x >= main_wa.bottom_right().x
+                || c.y >= main_wa.bottom_right().y))
+    }
+    // returns `true` if the provided rect is fully inside the bounds
+    // of any current work area boundaries, or `false` otherwise.
+    fn is_rect_inside_any_work_area<R: Into<Rect> + Copy>(r: R) -> bool {
+        let r: Rect = r.into();
+        Self::is_coord_inside_any_work_area(r.top_left())
+            && Self::is_coord_inside_any_work_area(r.bottom_right())
+    }
+
+    // returns `true` if the provided position is inside the bounds
+    // of any current screen xywh boundaries, or `false` otherwise.
+    fn is_coord_inside_any_xywh<C: Into<Coord> + Copy>(c: C) -> bool {
+        let c: Coord = c.into();
+        let main_xywh: Rect = screen_xywh(0).into();
+        // returns false if we get `0` but the coords are outside main screen's
+        !(screen_num(c.x, c.y) == 0
+            && (c.x < main_xywh.x
+                || c.y < main_xywh.y
+                || c.x >= main_xywh.bottom_right().x
+                || c.y >= main_xywh.bottom_right().y))
+    }
+    // returns `true` if the provided rect is fully inside the bounds
+    // of any current screeen xywh boundaries, or `false` otherwise.
+    fn is_rect_inside_any_xywh<R: Into<Rect> + Copy>(r: R) -> bool {
+        let r: Rect = r.into();
+        Self::is_coord_inside_any_xywh(r.top_left())
+            && Self::is_coord_inside_any_xywh(r.bottom_right())
+    }
+}
+
+// standalone functions
+
 /// Returns a pair of the width and height of the screen
 pub fn screen_size() -> (f64, f64) {
     unsafe { ((fl::Fl_screen_w() as f64), (fl::Fl_screen_h() as f64)) }
@@ -10,37 +334,50 @@ pub fn screen_coords() -> (i32, i32) {
     unsafe { (fl::Fl_screen_x(), fl::Fl_screen_y()) }
 }
 
-/// Set the screen scale
+/// Sets the screen scale
 pub fn set_screen_scale(n: i32, factor: f32) {
-    unsafe { fl::Fl_set_screen_scale(n as i32, factor) }
+    unsafe { fl::Fl_set_screen_scale(n, factor) }
 }
 
-/// Get the screen scale
-pub fn screen_scale(n: i32) -> f32 {
-    unsafe { fl::Fl_screen_scale(n as i32) }
+/// Returns the screen scale
+///
+/// If `screen_num` doesn't correspond to a valid screen number,
+/// the main screen's number (`0`) will be used instead.
+pub fn screen_scale(screen_num: i32) -> f32 {
+    unsafe { fl::Fl_screen_scale(screen_num) }
 }
 
-/// Return whether scaling the screen is supported
+/// Returns whether scaling the screen is supported
 pub fn screen_scaling_supported() -> bool {
     unsafe { fl::Fl_screen_scaling_supported() != 0 }
 }
 
-/// Get the screen count
+/// Returns the screen count
 pub fn screen_count() -> i32 {
-    unsafe { fl::Fl_screen_count() as i32 }
+    unsafe { fl::Fl_screen_count() }
 }
 
-/// Get the screen number based on its coordinates
+/// Returns the screen number of a screen that contains the specified position
+///
+/// If the coordinates are out of bounds, the main screen's number (`0`)
+/// will be returned instead.
 pub fn screen_num(x: i32, y: i32) -> i32 {
     unsafe { fl::Fl_screen_num(x, y) }
 }
 
-/// Get a screen's dpi resolution
-/// # Returns
-/// (vertical, horizontal) resolutions
+/// Returns the screen number for the screen which intersects the most with
+/// the provided rectangle
+pub fn screen_num_inside<R: Into<Rect> + Copy>(rect: R) -> i32 {
+    let r: Rect = rect.into();
+    unsafe { fl::Fl_screen_num_inside(r.x, r.y, r.w, r.h) }
+}
+
+/// Returns a screen's (vertical, horizontal) dpi resolution
+///
+/// If `screen_num` doesn't correspond to a valid screen number,
+/// the main screen's number (`0`) will be used instead.
 pub fn screen_dpi(screen_num: i32) -> (f32, f32) {
-    let mut h: f32 = 0.;
-    let mut v: f32 = 0.;
+    let (mut h, mut v) = (0_f32, 0_f32);
     unsafe {
         fl::Fl_screen_dpi(&mut h, &mut v, screen_num);
     }
@@ -48,11 +385,11 @@ pub fn screen_dpi(screen_num: i32) -> (f32, f32) {
 }
 
 /// Get a screen's xywh
+///
+/// If `screen_num` doesn't correspond to a valid screen number,
+/// the main screen's number (`0`) will be used instead.
 pub fn screen_xywh(screen_num: i32) -> (i32, i32, i32, i32) {
-    let mut x = 0;
-    let mut y = 0;
-    let mut w = 0;
-    let mut h = 0;
+    let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
     unsafe {
         fl::Fl_screen_xywh(&mut x, &mut y, &mut w, &mut h, screen_num);
     }
@@ -60,11 +397,11 @@ pub fn screen_xywh(screen_num: i32) -> (i32, i32, i32, i32) {
 }
 
 /// Get a screen's working area
+///
+/// If `screen_num` doesn't correspond to a valid screen number,
+/// the main screen's number (`0`) will be used instead.
 pub fn screen_work_area(screen_num: i32) -> (i32, i32, i32, i32) {
-    let mut x = 0;
-    let mut y = 0;
-    let mut w = 0;
-    let mut h = 0;
+    let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
     unsafe {
         fl::Fl_screen_work_area(&mut x, &mut y, &mut w, &mut h, screen_num);
     }
diff --git a/fltk/src/draw/coord.rs b/fltk/src/draw/coord.rs
new file mode 100644
index 00000000..6a06b661
--- /dev/null
+++ b/fltk/src/draw/coord.rs
@@ -0,0 +1,65 @@
+/// Defines a pair of `x, y` coordinates
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub struct Coordinates<T: Copy> {
+    /// Horizontal X coordinate
+    pub x: T,
+    /// Vertical Y coordinate
+    pub y: T,
+}
+
+/// `i32` Coordinates
+#[deprecated]
+pub struct Coord<T: Copy>(pub T, pub T);
+// pub type Coord = Coordinates<i32>; // TODO for 2.0
+
+/// `f64` Coordinates
+#[allow(non_camel_case_types)]
+pub type Coord_f64 = Coordinates<f64>;
+
+impl<T: Copy> Coordinates<T> {
+    /// Returns a new pair of `x, y` coordinates
+    pub fn new(x: T, y: T) -> Self {
+        Coordinates { x, y }
+    }
+
+    /// Returns `x` interpreted as width.
+    #[inline]
+    pub fn w(&self) -> T {
+        self.x
+    }
+    /// Returns `y` interpreted as height.
+    #[inline]
+    pub fn h(&self) -> T {
+        self.y
+    }
+}
+
+// Conversions From/Into array and tuple
+
+impl<T: Copy> From<[T; 2]> for Coordinates<T> {
+    fn from(array: [T; 2]) -> Self {
+        Self {
+            x: array[0],
+            y: array[1],
+        }
+    }
+}
+impl<T: Copy> From<Coordinates<T>> for [T; 2] {
+    fn from(c: Coordinates<T>) -> Self {
+        [c.x, c.y]
+    }
+}
+
+impl<T: Copy> From<(T, T)> for Coordinates<T> {
+    fn from(tuple: (T, T)) -> Self {
+        Self {
+            x: tuple.0,
+            y: tuple.1,
+        }
+    }
+}
+impl<T: Copy> From<Coordinates<T>> for (T, T) {
+    fn from(c: Coordinates<T>) -> Self {
+        (c.x, c.y)
+    }
+}
diff --git a/fltk/src/draw.rs b/fltk/src/draw/mod.rs
similarity index 97%
rename from fltk/src/draw.rs
rename to fltk/src/draw/mod.rs
index 997e60e4..45288056 100644
--- a/fltk/src/draw.rs
+++ b/fltk/src/draw/mod.rs
@@ -7,9 +7,10 @@ use std::ffi::{CStr, CString};
 use std::mem;
 use std::os::raw;
 
-/// Defines a coordinate of x and y
-#[derive(Copy, Clone, Debug)]
-pub struct Coord<T: Copy>(pub T, pub T);
+mod coord;
+pub use coord::{Coord, Coord_f64, Coordinates};
+mod rect;
+pub use rect::{Rect, Rect_f64, Rectangle};
 
 bitflags::bitflags! {
     /// Defines the line styles supported by fltk
@@ -258,8 +259,11 @@ pub fn draw_pie(x: i32, y: i32, width: i32, height: i32, a: f64, b: f64) {
 }
 
 /// Sets the line style
+///
 /// # Warning
-/// You are required to change this back to [`set_line_style(LineStyle::Solid, 0)`](`crate::draw::set_line_style`) after finishing
+/// You are required to change this back to
+/// [`set_line_style(LineStyle::Solid, 0)`](crate::draw::set_line_style)
+/// after finishing
 pub fn set_line_style(style: LineStyle, width: i32) {
     unsafe {
         crate::app::open_display();
@@ -412,7 +416,7 @@ pub fn draw_yxline2(x: i32, y: i32, y1: i32, x2: i32) {
     unsafe { Fl_yxline2(x, y, y1, x2) }
 }
 
-///  Draws a vertical line from (x,y) to (x,y1) then a horizontal from (x,y1)
+/// Draws a vertical line from (x,y) to (x,y1) then a horizontal from (x,y1)
 /// to (x2,y1), then another vertical from (x2,y1) to (x2,y3)
 pub fn draw_yxline3(x: i32, y: i32, y1: i32, x2: i32, y3: i32) {
     unsafe { Fl_yxline3(x, y, y1, x2, y3) }
@@ -561,8 +565,7 @@ pub fn width2(txt: &str, n: i32) -> f64 {
 /// Measure the width and height of a text
 pub fn measure(txt: &str, draw_symbols: bool) -> (i32, i32) {
     let txt = CString::safe_new(txt);
-    let mut x = 0;
-    let mut y = 0;
+    let (mut x, mut y) = (0, 0);
     unsafe {
         Fl_measure(txt.as_ptr(), &mut x, &mut y, draw_symbols as i32);
     }
@@ -570,24 +573,22 @@ pub fn measure(txt: &str, draw_symbols: bool) -> (i32, i32) {
 }
 
 /// Measure the width and height of a text
+///
 /// If `width` is non-zero, it will wrap to that width
 pub fn wrap_measure(txt: &str, width: i32, draw_symbols: bool) -> (i32, i32) {
     let txt = CString::safe_new(txt);
-    let mut x = width;
-    let mut y = 0;
+    let (mut x, mut y) = (width, 0);
     unsafe {
         Fl_measure(txt.as_ptr(), &mut x, &mut y, draw_symbols as i32);
     }
     (x, y)
 }
 
-/// Measure the coordinates and size of the text where a bounding box using the returned data would fit the text
+/// Measure the coordinates and size of the text where a bounding box using the
+/// returned data would fit the text
 pub fn text_extents(txt: &str) -> (i32, i32, i32, i32) {
     let txt = CString::safe_new(txt);
-    let mut x = 0;
-    let mut y = 0;
-    let mut w = 0;
-    let mut h = 0;
+    let (mut x, mut y, mut w, mut h) = (0, 0, 0, 0);
     unsafe {
         Fl_text_extents(txt.as_ptr(), &mut x, &mut y, &mut w, &mut h);
     }
@@ -661,8 +662,10 @@ pub fn rtl_draw(txt: &str, x: i32, y: i32) {
 }
 
 /// Draws a series of line segments around the given box.
-/// The string must contain groups of 4 letters which specify one of 24 standard grayscale values,
-/// where 'A' is black and 'X' is white. The order of each set of 4 characters is: top, left, bottom, right.
+///
+/// The string must contain groups of 4 letters which specify one of 24 standard
+/// grayscale values, where 'A' is black and 'X' is white.
+/// The order of each set of 4 characters is: top, left, bottom, right.
 pub fn draw_frame(string: &str, x: i32, y: i32, width: i32, height: i32) {
     assert!(string.len() % 4 == 0);
     let s = CString::safe_new(string);
@@ -670,6 +673,7 @@ pub fn draw_frame(string: &str, x: i32, y: i32, width: i32, height: i32) {
 }
 
 /// Draws a series of line segments around the given box
+///
 /// Differs from frame() by the order of the line segments which is bottom, right, top, left.
 pub fn draw_frame2(string: &str, x: i32, y: i32, width: i32, height: i32) {
     assert!(string.len() % 4 == 0);
diff --git a/fltk/src/draw/rect.rs b/fltk/src/draw/rect.rs
new file mode 100644
index 00000000..2f4e23c5
--- /dev/null
+++ b/fltk/src/draw/rect.rs
@@ -0,0 +1,91 @@
+use std::ops::{Add, Sub};
+
+use super::Coordinates;
+
+/// Defines a rectangular bounding box
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub struct Rectangle<T: Copy + Add<Output = T> + Sub<Output = T>> {
+    /// Leftmost corner
+    pub x: T,
+    /// Topmost corner
+    pub y: T,
+    /// Width
+    pub w: T,
+    /// Height
+    pub h: T,
+}
+
+/// `i32` Coordinates
+pub type Rect = Rectangle<i32>;
+
+/// `f64` Coordinates
+#[allow(non_camel_case_types)]
+pub type Rect_f64 = Rectangle<f64>;
+
+impl<T: Copy + Add<Output = T> + Sub<Output = T>> Rectangle<T> {
+    /// Returns a new `Rectangle` from its top-left corner position,
+    /// and the length of its sides.
+    pub fn new(x: T, y: T, width: T, height: T) -> Self {
+        Self {
+            x,
+            y,
+            w: width,
+            h: height,
+        }
+    }
+
+    /// Returns a new `Rectangle` from the position of its `top_left`
+    /// and `bottom_right` corners.
+    pub fn from_coords(top_left: Coordinates<T>, bottom_right: Coordinates<T>) -> Self {
+        Self {
+            x: top_left.x,
+            y: top_left.y,
+            w: bottom_right.x - top_left.x,
+            h: bottom_right.y - top_left.y,
+        }
+    }
+
+    /// Returns the coordinates of the top-left corner
+    pub fn top_left(&self) -> Coordinates<T> {
+        Coordinates::new(self.x, self.y)
+    }
+
+    /// Returns the coordinates of the bottom-right corner
+    pub fn bottom_right(&self) -> Coordinates<T> {
+        Coordinates::new(self.x + self.w, self.y + self.h)
+    }
+}
+
+// Conversions From/Into array and tuple
+
+impl<T: Copy + Add<Output = T> + Sub<Output = T>> From<[T; 4]> for Rectangle<T> {
+    fn from(array: [T; 4]) -> Self {
+        Self {
+            x: array[0],
+            y: array[1],
+            w: array[2],
+            h: array[3],
+        }
+    }
+}
+impl<T: Copy + Add<Output = T> + Sub<Output = T>> From<Rectangle<T>> for [T; 4] {
+    fn from(c: Rectangle<T>) -> Self {
+        [c.x, c.y, c.w, c.h]
+    }
+}
+
+impl<T: Copy + Add<Output = T> + Sub<Output = T>> From<(T, T, T, T)> for Rectangle<T> {
+    fn from(tuple: (T, T, T, T)) -> Self {
+        Self {
+            x: tuple.0,
+            y: tuple.1,
+            w: tuple.2,
+            h: tuple.3,
+        }
+    }
+}
+impl<T: Copy + Add<Output = T> + Sub<Output = T>> From<Rectangle<T>> for (T, T, T, T) {
+    fn from(c: Rectangle<T>) -> Self {
+        (c.x, c.y, c.w, c.h)
+    }
+}


Test patch: