Files
another-boids-in-rust/vendor/x11rb/examples/xeyes.rs

373 lines
11 KiB
Rust

// This is (quite loosely) based on the original xeyes
extern crate x11rb;
use x11rb::connection::{Connection, RequestConnection as _};
use x11rb::errors::{ConnectionError, ReplyOrIdError};
use x11rb::protocol::shape::{self, ConnectionExt as _};
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::COPY_DEPTH_FROM_PARENT;
const PUPIL_SIZE: i16 = 50;
const EYE_SIZE: i16 = 50;
// Draw the big background of the eyes
fn draw_eyes<C: Connection>(
conn: &C,
win_id: Window,
black: Gcontext,
white: Gcontext,
window_size: (u16, u16),
) -> Result<(), ConnectionError> {
// Draw the black outlines
let mut arc1 = Arc {
x: 0,
y: 0,
width: window_size.0 / 2,
height: window_size.1,
angle1: 0,
angle2: 360 * 64,
};
let mut arc2 = arc1;
arc2.x = arc2.width as _;
conn.poly_fill_arc(win_id, black, &[arc1, arc2])?;
// Draw the white inner part
for arc in [&mut arc1, &mut arc2].iter_mut() {
arc.x += EYE_SIZE;
arc.y += EYE_SIZE;
arc.width -= 2 * EYE_SIZE as u16;
arc.height -= 2 * EYE_SIZE as u16;
}
conn.poly_fill_arc(win_id, white, &[arc1, arc2])?;
Ok(())
}
// Draw the pupils inside the eye
fn draw_pupils<C: Connection>(
conn: &C,
win_id: Window,
gc: Gcontext,
((x1, y1), (x2, y2)): ((i16, i16), (i16, i16)),
) -> Result<(), ConnectionError> {
// Transform center to top left corner
let (x1, y1) = (x1 - PUPIL_SIZE / 2, y1 - PUPIL_SIZE / 2);
let (x2, y2) = (x2 - PUPIL_SIZE / 2, y2 - PUPIL_SIZE / 2);
let arc1 = Arc {
x: x1,
y: y1,
width: PUPIL_SIZE as _,
height: PUPIL_SIZE as _,
angle1: 90 * 64,
angle2: 360 * 64,
};
let mut arc2 = arc1;
arc2.x = x2;
arc2.y = y2;
// Do the drawing
conn.poly_fill_arc(win_id, gc, &[arc1, arc2])?;
Ok(())
}
// Given two points, return their squared distance
fn distance_squared(p1: (f64, f64), p2: (f64, f64)) -> f64 {
let dx = p1.0 - p2.0;
let dy = p1.1 - p2.1;
dx * dx + dy * dy
}
// Compute the position of a pupil inside of the given area.
fn compute_pupil(area: (i16, i16, i16, i16), mouse: (i16, i16)) -> (i16, i16) {
// What is the center of the eye?
let center_x = area.0 + area.2 / 2;
let center_y = area.1 + area.3 / 2;
let (w, h) = (f64::from(area.2) / 2.0, f64::from(area.3) / 2.0);
// Is the mouse exactly on the center?
if (center_x, center_y) == mouse {
return mouse;
}
let center = (f64::from(center_x), f64::from(center_y));
let mouse = (f64::from(mouse.0), f64::from(mouse.1));
// Calculate the offset of the mouse position from the center
let diff = (mouse.0 - center.0, mouse.1 - center.1);
// An eclipse is described by this equation, where the angle 'a' is varied over all values, but
// does not actually describe the angle from the center due to the different scaling in x and y
// direction.
//
// x = w * cos(a)
// y = h * sin(a)
//
// With tan(a) = sin(a)/cos(a), we get
//
// tan(a) * x = w * sin(a) => sin(a) = tan(a) * x / w
// y = h * sin(a) => sin(a) = y / h
//
// and thus
//
// tan(a) * x / w = y / h
//
// which we can rearrange to
//
// tan(a) = (y * w) / (x * h)
//
// And thus, the angle we are looking for is:
//
// a = arctan((y * w) / (x * h))
//
// However, due to tan() being the way it is, we actually need:
let angle = (diff.1 * w).atan2(diff.0 * h);
// Now compute the corresponding point on the ellipse (relative to the center)
let (cx, cy) = (w * angle.cos(), h * angle.sin());
// ...and also compute the actual point
let (x, y) = ((center.0 + cx) as _, (center.1 + cy) as _);
// Return the point that is closer to the center
if distance_squared(center, mouse) < distance_squared(center, (x, y)) {
(mouse.0 as _, mouse.1 as _)
} else {
(x as _, y as _)
}
}
// Compute the position of both pupils.
fn compute_pupils(window_size: (u16, u16), mouse_position: (i16, i16)) -> ((i16, i16), (i16, i16)) {
let border = PUPIL_SIZE + EYE_SIZE;
let half_width = window_size.0 as i16 / 2;
let width = half_width - 2 * border;
let height = window_size.1 as i16 - 2 * border;
(
compute_pupil((border, border, width, height), mouse_position),
compute_pupil((border + half_width, border, width, height), mouse_position),
)
}
fn shape_window<C: Connection>(
conn: &C,
win_id: Window,
window_size: (u16, u16),
) -> Result<(), ReplyOrIdError> {
// Create a pixmap for the shape
let pixmap = PixmapWrapper::create_pixmap(conn, 1, win_id, window_size.0, window_size.1)?;
// Fill the pixmap with what will indicate "transparent"
let gc = create_gc_with_foreground(conn, pixmap.pixmap(), 0)?;
let rect = Rectangle {
x: 0,
y: 0,
width: window_size.0,
height: window_size.1,
};
conn.poly_fill_rectangle(pixmap.pixmap(), gc.gcontext(), &[rect])?;
// Draw the eyes as "not transparent"
let values = ChangeGCAux::new().foreground(1);
conn.change_gc(gc.gcontext(), &values)?;
draw_eyes(
conn,
pixmap.pixmap(),
gc.gcontext(),
gc.gcontext(),
window_size,
)?;
// Set the shape of the window
conn.shape_mask(shape::SO::SET, shape::SK::BOUNDING, win_id, 0, 0, &pixmap)?;
Ok(())
}
fn setup_window<C: Connection>(
conn: &C,
screen: &Screen,
window_size: (u16, u16),
wm_protocols: Atom,
wm_delete_window: Atom,
) -> Result<Window, ReplyOrIdError> {
let win_id = conn.generate_id()?;
let win_aux = CreateWindowAux::new()
.event_mask(EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::POINTER_MOTION)
.background_pixel(screen.white_pixel);
conn.create_window(
COPY_DEPTH_FROM_PARENT,
win_id,
screen.root,
0,
0,
window_size.0,
window_size.1,
0,
WindowClass::INPUT_OUTPUT,
0,
&win_aux,
)?;
let title = "xeyes";
conn.change_property8(
PropMode::REPLACE,
win_id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
)?;
conn.change_property32(
PropMode::REPLACE,
win_id,
wm_protocols,
AtomEnum::ATOM,
&[wm_delete_window],
)?;
conn.map_window(win_id)?;
Ok(win_id)
}
fn create_gc_with_foreground<C: Connection>(
conn: C,
win_id: Window,
foreground: u32,
) -> Result<GcontextWrapper<C>, ReplyOrIdError> {
GcontextWrapper::create_gc(
conn,
win_id,
&CreateGCAux::new()
.graphics_exposures(0)
.foreground(foreground),
)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = connect(None).expect("Failed to connect to the X11 server");
// The following is only needed for start_timeout_thread(), which is used for 'tests'
let conn = std::sync::Arc::new(conn);
let screen = &conn.setup().roots[screen_num];
let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS")?;
let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW")?;
let mut window_size = (700, 500);
let has_shape = conn
.extension_information(shape::X11_EXTENSION_NAME)
.expect("failed to get extension information")
.is_some();
let (wm_protocols, wm_delete_window) =
(wm_protocols.reply()?.atom, wm_delete_window.reply()?.atom);
let win_id = setup_window(&conn, screen, window_size, wm_protocols, wm_delete_window)?;
let mut pixmap = PixmapWrapper::create_pixmap(
conn.clone(),
screen.root_depth,
win_id,
window_size.0,
window_size.1,
)?;
let black_gc = create_gc_with_foreground(&conn, win_id, screen.black_pixel)?;
let white_gc = create_gc_with_foreground(&conn, win_id, screen.white_pixel)?;
conn.flush()?;
let mut need_repaint = false;
let mut need_reshape = false;
let mut mouse_position = (0, 0);
util::start_timeout_thread(conn.clone(), win_id);
loop {
let event = conn.wait_for_event()?;
let mut event_option = Some(event);
while let Some(event) = event_option {
match event {
Event::Expose(event) => {
if event.count == 0 {
need_repaint = true;
}
}
Event::ConfigureNotify(event) => {
window_size = (event.width, event.height);
pixmap = PixmapWrapper::create_pixmap(
conn.clone(),
screen.root_depth,
win_id,
window_size.0,
window_size.1,
)?;
need_reshape = true;
}
Event::MotionNotify(event) => {
mouse_position = (event.event_x, event.event_y);
need_repaint = true;
}
Event::MapNotify(_) => {
need_reshape = true;
}
Event::ClientMessage(event) => {
let data = event.data.as_data32();
if event.format == 32 && event.window == win_id && data[0] == wm_delete_window {
println!("Window was asked to close");
return Ok(());
}
}
Event::Error(error) => {
println!("Unknown error {error:?}");
}
event => {
println!("Unknown event {event:?}");
}
}
event_option = conn.poll_for_event()?;
}
if need_reshape && has_shape {
shape_window(&conn, win_id, window_size)?;
need_reshape = false;
}
if need_repaint {
// Draw new pupils
let pos = compute_pupils(window_size, mouse_position);
draw_eyes(
&conn,
pixmap.pixmap(),
black_gc.gcontext(),
white_gc.gcontext(),
window_size,
)?;
draw_pupils(&conn, pixmap.pixmap(), black_gc.gcontext(), pos)?;
// Copy drawing from pixmap to window
conn.copy_area(
pixmap.pixmap(),
win_id,
white_gc.gcontext(),
0,
0,
0,
0,
window_size.0,
window_size.1,
)?;
conn.flush()?;
need_repaint = false;
}
}
}
include!("integration_test_util/connect.rs");
include!("integration_test_util/util.rs");