|
| 1 | +#![no_std] |
| 2 | +#![cfg_attr(not(test), no_main)] |
| 3 | + |
| 4 | +extern crate alloc; |
| 5 | +extern crate display_client; |
| 6 | + |
| 7 | +#[allow(unused_imports)] |
| 8 | +#[macro_use] |
| 9 | +extern crate ulib; |
| 10 | + |
| 11 | +use core::ops::ControlFlow; |
| 12 | + |
| 13 | +use display_client::proto; |
| 14 | +use glam::IVec2; |
| 15 | + |
| 16 | +fn draw_pixel(fb: &mut [u32], stride: usize, pos: IVec2, color: u32) { |
| 17 | + let height = fb.len() as i32 / stride as i32; |
| 18 | + let pos = pos.clamp(IVec2::ZERO, IVec2::new(stride as i32, height as i32)); |
| 19 | + fb[(pos.y as usize * stride + pos.x as usize).clamp(0, fb.len() - 1)] = color; |
| 20 | +} |
| 21 | + |
| 22 | +fn get_pixel(fb: &mut [u32], stride: usize, pos: IVec2) -> u32 { |
| 23 | + let height = fb.len() as i32 / stride as i32; |
| 24 | + let pos = pos.clamp(IVec2::ZERO, IVec2::new(stride as i32, height as i32)); |
| 25 | + fb[(pos.y as usize * stride + pos.x as usize).clamp(0, fb.len() - 1)] |
| 26 | +} |
| 27 | + |
| 28 | +fn draw_circle(fb: &mut [u32], stride: usize, pos: IVec2, color: u32, r: u32) { |
| 29 | + let r = r as i32; |
| 30 | + for x in -r..=r { |
| 31 | + for y in -r..=r { |
| 32 | + if x * x + y * y <= r { |
| 33 | + draw_pixel(fb, stride, pos + IVec2::new(x, y), color); |
| 34 | + } |
| 35 | + } |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +#[no_mangle] |
| 40 | +fn main(argc: usize, argv: *const *const u8) { |
| 41 | + let argv_array = unsafe { core::slice::from_raw_parts(argv, argc) }; |
| 42 | + let _args = argv_array |
| 43 | + .iter() |
| 44 | + .copied() |
| 45 | + .map(|arg| unsafe { core::ffi::CStr::from_ptr(arg) }.to_bytes()) |
| 46 | + .map(|arg| core::str::from_utf8(arg).unwrap()) |
| 47 | + .collect::<alloc::vec::Vec<_>>(); |
| 48 | + |
| 49 | + let mut buf = display_client::connect(512, 384); |
| 50 | + buf.set_title("paint".as_bytes()); |
| 51 | + |
| 52 | + let (width, height) = ( |
| 53 | + buf.video_meta.width as usize, |
| 54 | + buf.video_meta.height as usize, |
| 55 | + ); |
| 56 | + let row_stride = buf.video_meta.row_stride as usize / 4; |
| 57 | + |
| 58 | + let mut down = false; |
| 59 | + let mut pos = IVec2::new(0, 0); |
| 60 | + let mut radius = 3; |
| 61 | + let mut color = 0xFF000000; |
| 62 | + |
| 63 | + buf.video_mem().fill(0xFFFFFFFF); |
| 64 | + |
| 65 | + let color_palette = [ |
| 66 | + 0xFF000000, 0xFFFFFFFF, 0xFF464646, 0xFFDCDCDC, 0xFF787878, 0xFFB4B4B4, 0xFF990030, |
| 67 | + 0xFF9C5A3C, 0xFFED1C24, 0xFFFFA3B1, 0xFFFF7E00, 0xFFE5AA7A, 0xFFFFC20E, 0xFFF5E49C, |
| 68 | + 0xFFFFF200, 0xFFFFF9BD, 0xFFA8E61D, 0xFFD3F9BC, 0xFF22B14C, 0xFF9DBB61, 0xFF00B7EF, |
| 69 | + 0xFF99D9EA, 0xFF4D6DF3, 0xFF709AD1, 0xFF2F3699, 0xFF546D8E, 0xFF6F3198, 0xFFB5A5D5, |
| 70 | + ]; |
| 71 | + |
| 72 | + let fb = buf.video_mem(); |
| 73 | + let palette_height = 16; |
| 74 | + let palette_elems = 14; |
| 75 | + for r in height - palette_height..height { |
| 76 | + for c in 0..width { |
| 77 | + let idx = (c * palette_elems) / width; |
| 78 | + fb[r * row_stride + c] = color_palette[idx * 2]; |
| 79 | + } |
| 80 | + } |
| 81 | + for r in height - 2 * palette_height..height - palette_height { |
| 82 | + for c in 0..width { |
| 83 | + let idx = (c * palette_elems) / width; |
| 84 | + fb[r * row_stride + c] = color_palette[idx * 2 + 1]; |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + 'outer: loop { |
| 89 | + while let Some(ev) = buf.server_to_client_queue().try_recv() { |
| 90 | + match ev.kind { |
| 91 | + proto::EventKind::INPUT => { |
| 92 | + use proto::EventData; |
| 93 | + let data = proto::InputEvent::parse(&ev).expect("TODO"); |
| 94 | + if let ControlFlow::Break(_) = handle_input( |
| 95 | + data, |
| 96 | + &mut buf, |
| 97 | + row_stride, |
| 98 | + &mut down, |
| 99 | + &mut pos, |
| 100 | + &mut color, |
| 101 | + &mut radius, |
| 102 | + ) { |
| 103 | + break 'outer; |
| 104 | + } |
| 105 | + } |
| 106 | + proto::EventKind::REQUEST_CLOSE => { |
| 107 | + break 'outer; |
| 108 | + } |
| 109 | + _ => (), |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + buf.client_to_server_queue() |
| 114 | + .try_send(proto::Event { |
| 115 | + kind: proto::EventKind::PRESENT, |
| 116 | + data: [0; 7], |
| 117 | + }) |
| 118 | + .ok(); |
| 119 | + |
| 120 | + // signal(video)? for sync |
| 121 | + ulib::sys::sem_down(buf.get_sem_fd(buf.present_sem)).unwrap(); |
| 122 | + } |
| 123 | + |
| 124 | + buf.client_to_server_queue() |
| 125 | + .try_send(proto::Event { |
| 126 | + kind: proto::EventKind::DISCONNECT, |
| 127 | + data: [0; 7], |
| 128 | + }) |
| 129 | + .ok(); |
| 130 | +} |
| 131 | + |
| 132 | +fn handle_input( |
| 133 | + data: proto::InputEvent, |
| 134 | + buf: &mut proto::BufferHandle, |
| 135 | + row_stride: usize, |
| 136 | + down: &mut bool, |
| 137 | + pos: &mut IVec2, |
| 138 | + color: &mut u32, |
| 139 | + radius: &mut u32, |
| 140 | +) -> ControlFlow<()> { |
| 141 | + if data.kind == proto::InputEvent::KIND_KEY { |
| 142 | + match proto::ScanCode(data.data2) { |
| 143 | + proto::ScanCode::ESCAPE | proto::ScanCode::Q => { |
| 144 | + return ControlFlow::Break(()); |
| 145 | + } |
| 146 | + _ => (), |
| 147 | + } |
| 148 | + } else if data.kind == proto::InputEvent::KIND_MOUSE { |
| 149 | + let mode = data.data1; |
| 150 | + let x = data.data2 as i32; |
| 151 | + let y = data.data3 as i32; |
| 152 | + let new_pos = IVec2::new(x, y); |
| 153 | + let button = data.data4; |
| 154 | + match mode { |
| 155 | + proto::InputEvent::MODE_MOUSE_MOVE => { |
| 156 | + if *down { |
| 157 | + let distance = (new_pos - *pos).abs().max_element(); |
| 158 | + // TODO: sqrt |
| 159 | + for i in (0..=128).step_by((128 / distance.max(1) as usize).max(1)) { |
| 160 | + let p = (*pos * (128 - i) + new_pos * i) / 128; |
| 161 | + draw_circle(buf.video_mem(), row_stride, p, *color, *radius); |
| 162 | + } |
| 163 | + } |
| 164 | + } |
| 165 | + proto::InputEvent::MODE_MOUSE_DOWN => { |
| 166 | + if button == 1 { |
| 167 | + *down = true; |
| 168 | + draw_circle(buf.video_mem(), row_stride, new_pos, *color, *radius); |
| 169 | + } |
| 170 | + if button == 3 { |
| 171 | + *color = get_pixel(buf.video_mem(), row_stride, new_pos); |
| 172 | + } |
| 173 | + } |
| 174 | + proto::InputEvent::MODE_MOUSE_UP => { |
| 175 | + if button == 1 { |
| 176 | + *down = false; |
| 177 | + } |
| 178 | + } |
| 179 | + _ => (), |
| 180 | + } |
| 181 | + *pos = new_pos; |
| 182 | + } else if data.kind == proto::InputEvent::KIND_SCROLL { |
| 183 | + // TODO: log scale using only integers |
| 184 | + *radius = radius.saturating_add_signed(data.data1 as i32); |
| 185 | + } |
| 186 | + ControlFlow::Continue(()) |
| 187 | +} |
0 commit comments