From f06b625998d4e914e63ec854a24b47e52357cd37 Mon Sep 17 00:00:00 2001 From: Georg Hopp Date: Sun, 22 Dec 2019 00:04:28 +0100 Subject: [PATCH] Add first simple triable animation based on fractionals --- fractional/Cargo.toml | 4 + fractional/src/easel.rs | 10 +- fractional/src/lib.rs | 1 + fractional/src/main.rs | 56 ++++++++++ fractional/src/xcb.rs | 232 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 fractional/src/xcb.rs diff --git a/fractional/Cargo.toml b/fractional/Cargo.toml index 44b056f..d1ca54c 100644 --- a/fractional/Cargo.toml +++ b/fractional/Cargo.toml @@ -6,3 +6,7 @@ edition = "2018" [dependencies] lazy_static = "1.4.0" +libc = "0.2" +gl = "0.5.2" +x11 = { version = "2.3", features = ["glx"] } +xcb = { version = "0.8", features = ["dri2", "randr", "thread", "xlib_xcb", "shm"] } diff --git a/fractional/src/easel.rs b/fractional/src/easel.rs index 405ae1d..d203f65 100644 --- a/fractional/src/easel.rs +++ b/fractional/src/easel.rs @@ -20,13 +20,19 @@ // use std::cmp; use std::fmt::{Formatter, Display, Result}; +use std::sync::mpsc; pub trait Easel { - fn canvas(self) -> dyn Canvas; + //fn canvas(&mut self, width :u16, height :u16) -> Option<&dyn Canvas>; } pub trait Canvas { - fn draw(self, c :&dyn Drawable, ofs :Coordinate); + fn init_events(&self); + fn start_events(&self, tx :mpsc::Sender); + + fn clear(&mut self); + fn draw(&mut self, c :&dyn Drawable, ofs :Coordinate); + fn show(&self); } pub trait Drawable { diff --git a/fractional/src/lib.rs b/fractional/src/lib.rs index e04b8c2..e2c5a8a 100644 --- a/fractional/src/lib.rs +++ b/fractional/src/lib.rs @@ -28,6 +28,7 @@ pub mod fractional; pub mod transform; pub mod trigonometry; pub mod vector; +pub mod xcb; use fractional::Fractional; use vector::Vector; diff --git a/fractional/src/main.rs b/fractional/src/main.rs index c407919..29cdcad 100644 --- a/fractional/src/main.rs +++ b/fractional/src/main.rs @@ -23,6 +23,9 @@ use std::f64::consts::PI as FPI; use std::fmt::Display; use std::num::TryFromIntError; use std::ops::{Add,Sub,Neg,Mul,Div}; +use std::sync::mpsc; +use std::time; +use std::thread; use fractional::continuous::Continuous; use fractional::easel::{ Coordinate, Coordinates, Drawable, Line, Polyline @@ -32,6 +35,9 @@ use fractional::trigonometry::Trig; use fractional::vector::{Vector}; use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, rotate_v}; +use fractional::xcb::XcbEasel; +use fractional::easel::Canvas; + fn mean(v: &Vec) -> Result { let r = v.iter().fold(0, |acc, x| acc + x); let l = i64::try_from(v.len())?; @@ -325,4 +331,54 @@ fn main() { _transform2(); println!(); _line(); + + let xcb = XcbEasel::new().unwrap(); + let mut canvas = xcb.canvas(151, 151).unwrap(); + + canvas.set_title("Something..."); + canvas.init_events(); + + let (tx, rx) = mpsc::channel(); + let tx1 = mpsc::Sender::clone(&tx); + canvas.start_events(tx); + + let i = Vector(Fractional( 0,1), Fractional(-35,1), Fractional(0,1)); + let j = Vector(Fractional( 30,1), Fractional( 17,1), Fractional(0,1)); + let k = Vector(Fractional(-30,1), Fractional( 17,1), Fractional(0,1)); + + fn to_i32(x :Fractional) -> i32 { + let Fractional(n, d) = x; + (n / d + if (n % d).abs() < (n / 2).abs() { 0 } else { 1 }) as i32 + } + + thread::spawn(move || { + loop { + tx1.send(0).unwrap(); + thread::sleep(time::Duration::from_millis(10)); + } + }); + + let mut deg :i32 = 0; + + for x in rx { + match x { + 1 => break, + _ => { + let rot :TMatrix = rotate_z(deg); + let Vector(ix, iy, _) = rot.apply(&i); + let Vector(jx, jy, _) = rot.apply(&j); + let Vector(kx, ky, _) = rot.apply(&k); + + let pg = Polygon( + Coordinates(vec!( Coordinate(to_i32(ix), to_i32(iy)) + , Coordinate(to_i32(jx), to_i32(jy)) + , Coordinate(to_i32(kx), to_i32(ky)) ))); + + canvas.clear(); + canvas.draw(&pg, Coordinate(75,75)); + canvas.show(); + deg = (deg + 3) % 359; + }, + } + } } diff --git a/fractional/src/xcb.rs b/fractional/src/xcb.rs new file mode 100644 index 0000000..69d960d --- /dev/null +++ b/fractional/src/xcb.rs @@ -0,0 +1,232 @@ +// +// XCB implementation for drawing some stuff... +// +// Georg Hopp +// +// Copyright © 2019 Georg Hopp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +extern crate xcb; + +use std::sync::Arc; +use std::ptr; //::{null, null_mut}; +use std::thread; +use std::sync::mpsc; + +use crate::easel::{Easel, Canvas, Drawable, Coordinate, Coordinates}; + +#[derive(Clone)] +pub struct XcbEasel (Arc, i32); + +pub struct XcbCanvas<'a> { conn :Arc + , width :u16 + , height :u16 + , window :u32 + , pixmap :u32 + , gc :u32 + , shm :&'a mut [u32] } + +impl XcbEasel { + pub fn new() -> Result { + let (conn, num) = xcb::Connection::connect(None)?; + + Ok(XcbEasel(Arc::new(conn), num)) + } + + pub fn setup(&self) -> xcb::Setup { + let XcbEasel(conn, _) = self; + conn.get_setup() + } + + pub fn screen(&self) -> Option { + let XcbEasel(_, num) = self; + self.setup().roots().nth(*num as usize) + } + + pub fn canvas(&self, width :u16, height :u16) -> Option { + let Self(conn, _) = self; + let conn = conn.clone(); + let screen = match self.screen() { + None => return None, + Some(screen) => screen, + }; + + let shmseg = conn.generate_id(); + let gc = conn.generate_id(); + let pixmap = conn.generate_id(); + let window = conn.generate_id(); + + xcb::create_window( &conn, xcb::COPY_FROM_PARENT as u8, window + , screen.root(), 0, 0, width, width, 0 + , xcb::WINDOW_CLASS_INPUT_OUTPUT as u16 + , screen.root_visual() + , &[(xcb::CW_BACK_PIXEL, screen.white_pixel())] ); + + xcb::create_gc( &conn, gc, screen.root() + , &[ (xcb::GC_FOREGROUND, screen.black_pixel()) + , (xcb::GC_GRAPHICS_EXPOSURES, 0) ] ); + + let (shmid, shm) = getshm((width * height) as usize); + xcb::shm::attach(&conn, shmseg, shmid as u32, false); + unsafe { libc::shmctl(shmid, libc::IPC_RMID, ptr::null_mut()); } + + xcb::shm::create_pixmap( &conn, pixmap, window, width, height + , screen.root_depth(), shmseg, 0 ); + + xcb::map_window(&conn, window); + conn.flush(); + + Some(XcbCanvas{ conn: conn + , width: width + , height: height + , window: window + , pixmap: pixmap + , gc: gc + , shm: shm } ) + } +} + +impl<'a> XcbCanvas<'a> { + pub fn set_title(&self, title :&str) { + let c = xcb::change_property_checked( &self.conn + , xcb::PROP_MODE_REPLACE as u8 + , self.window + , xcb::ATOM_WM_NAME + , xcb::ATOM_STRING + , 8 + , title.as_bytes() ); + if self.conn.has_error().is_err() || c.request_check().is_err() { + println!("Error setting title"); + } + } +} + +fn getshm<'a>(size :usize) -> (i32, &'a mut [u32]) { + use std::slice::from_raw_parts_mut; + + unsafe { + let id = libc::shmget( libc::IPC_PRIVATE + , size * 4 + , libc::IPC_CREAT | 0o744 ); + let ptr = libc::shmat(id, ptr::null(), 0); + (id as i32, from_raw_parts_mut(ptr as *mut u32, size)) + } +} + +impl Easel for XcbEasel {} + +impl<'a> Canvas for XcbCanvas<'a> { + fn init_events(&self) { + let mask = [( xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE + | xcb::EVENT_MASK_KEY_PRESS + | xcb::EVENT_MASK_STRUCTURE_NOTIFY + | xcb::EVENT_MASK_PROPERTY_CHANGE )]; + xcb::change_window_attributes(&self.conn, self.window, &mask); + self.conn.flush(); + } + + fn start_events(&self, tx :mpsc::Sender) { + let conn = self.conn.clone(); + let window = self.window; + let pixmap = self.pixmap; + let gc = self.gc; + let width = self.width; + let height = self.height; + + thread::spawn(move || { + loop { + let event = conn.wait_for_event(); + + match event { + None => break, + Some(event) => { + match event.response_type() & !0x80 { + xcb::PROPERTY_NOTIFY => { + let prop_notify :&xcb::PropertyNotifyEvent + = unsafe { xcb::cast_event(&event) }; + + if prop_notify.atom() == xcb::ATOM_WM_NAME { + // retrieving title + let cookie + = xcb::get_property( &conn + , false + , window + , xcb::ATOM_WM_NAME + , xcb::ATOM_STRING + , 0, 1024 ); + + if let Ok(reply) = cookie.get_reply() { + let r = reply.value(); + let r = std::str::from_utf8(r).unwrap(); + + println!("title changed to: {}", r); + } + } + }, + + xcb::EXPOSE => { + xcb::copy_area( &conn, pixmap, window, gc + , 0, 0, 0, 0 + , width, height ); + conn.flush(); + }, + + xcb::KEY_PRESS => { + let key_press: &xcb::KeyPressEvent + = unsafe { xcb::cast_event(&event) }; + + println!( "Key '{}' pressed" + , key_press.detail() ); + + // Q (on qwerty) + if key_press.detail() == 0x18 { + tx.send(1).unwrap(); + break; + } + }, + + _ => {}, + } + }, + } + } + }); + } + + fn clear(&mut self) { + unsafe { + let ptr = self.shm.as_mut_ptr(); + ptr::write_bytes( ptr, 0 + , self.width as usize * self.height as usize); + } + } + + fn draw(&mut self, d :&dyn Drawable, ofs :Coordinate) { + let Coordinates(c) = d.plot(); + let Coordinate(xofs, yofs) = ofs; + + for Coordinate(x, y) in c { + let idx :usize = ((y+yofs)*(self.width as i32)+x+xofs) as usize; + self.shm[idx] = 0xFFFFFF; + } + } + + fn show(&self) { + xcb::copy_area( &self.conn, self.pixmap, self.window, self.gc + , 0, 0, 0, 0 + , self.width, self.height ); + self.conn.flush(); + } +}