Initial commit

This commit is contained in:
vee1e 2025-11-12 22:37:16 +05:30
commit 9d9efab167
7 changed files with 2699 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/release

2431
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "moomer"
version = "0.1.0"
edition = "2021"
[dependencies]
screenshots = "0.8"
winit = "0.28"
pixels = "0.13"
image = "0.24"

50
README.md Normal file
View file

@ -0,0 +1,50 @@
# Moomer
Zoomer application for macOS. A poor man's clone of [tsoding/boomer](https://github.com/tsoding/boomer).
## Quick Start
```bash
$ cargo build --release
$ cargo run --release
```
## Controls
| Control | Description |
|-------------------------------|---------------------------------------|
| <kbd>q</kbd> | Quit the application. |
| Drag with left mouse button | Move the image around. |
| Scroll wheel or trackpad | Zoom in/out (centered on cursor). |
## Features
- Takes a screenshot of your primary display on startup
- Fullscreen viewing without window decorations
- Smooth zoom with trackpad gestures
- Native macOS trackpad support
- Pan by clicking and dragging
## Building
### Dependencies
```bash
$ cargo build --release
```
The following Rust crates are used:
- `screenshots` - Screen capture
- `winit` - Window management and input handling
- `pixels` - Pixel buffer rendering
- `image` - Image processing
## Running
```bash
$ cargo run --release
```
The application will immediately capture your screen and display it in fullscreen mode. Use your trackpad or mouse to zoom and pan around the screenshot.

202
src/main.rs Normal file
View file

@ -0,0 +1,202 @@
use pixels::{Pixels, SurfaceTexture};
use screenshots::Screen;
use winit::{
event::{Event, WindowEvent, MouseScrollDelta, TouchPhase, ElementState, VirtualKeyCode},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowExtMacOS;
struct ViewState {
zoom: f32,
offset_x: f32,
offset_y: f32,
drag_start: Option<(f32, f32)>,
last_cursor_pos: (f32, f32),
}
impl ViewState {
fn new() -> Self {
Self {
zoom: 1.0,
offset_x: 0.0,
offset_y: 0.0,
drag_start: None,
last_cursor_pos: (0.0, 0.0),
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let screens = Screen::all()?;
let screen = screens.first().ok_or("No screens found")?;
let screenshot = screen.capture()?;
let screenshot_width = screenshot.width();
let screenshot_height = screenshot.height();
let img_buffer = image::RgbaImage::from_raw(
screenshot_width,
screenshot_height,
screenshot.to_vec(),
)
.ok_or("Failed to create image buffer")?;
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Screenshot Viewer")
.with_decorations(false)
.build(&event_loop)?;
#[cfg(target_os = "macos")]
window.set_simple_fullscreen(true);
let window_size = window.inner_size();
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
let mut pixels = Pixels::new(window_size.width, window_size.height, surface_texture)?;
let mut view_state = ViewState::new();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
WindowEvent::Resized(new_size) => {
if new_size.width > 0 && new_size.height > 0 {
let _ = pixels.resize_surface(new_size.width, new_size.height);
let _ = pixels.resize_buffer(new_size.width, new_size.height);
window.request_redraw();
}
}
WindowEvent::CursorMoved { position, .. } => {
view_state.last_cursor_pos = (position.x as f32, position.y as f32);
if let Some((start_x, start_y)) = view_state.drag_start {
let dx = position.x as f32 - start_x;
let dy = position.y as f32 - start_y;
view_state.offset_x += dx;
view_state.offset_y += dy;
view_state.drag_start = Some((position.x as f32, position.y as f32));
window.request_redraw();
}
}
WindowEvent::MouseInput { state, button, .. } => {
match state {
winit::event::ElementState::Pressed => {
if button == winit::event::MouseButton::Left {
view_state.drag_start = Some(view_state.last_cursor_pos);
}
}
winit::event::ElementState::Released => {
view_state.drag_start = None;
}
}
}
WindowEvent::MouseWheel { delta, phase, .. } => {
if phase == TouchPhase::Moved || phase == TouchPhase::Started {
match delta {
MouseScrollDelta::LineDelta(_, y) => {
let zoom_factor = 1.0 + (y * 0.1);
view_state.zoom *= zoom_factor;
view_state.zoom = view_state.zoom.max(0.1).min(10.0);
let cursor_x = view_state.last_cursor_pos.0;
let cursor_y = view_state.last_cursor_pos.1;
view_state.offset_x = cursor_x - (cursor_x - view_state.offset_x) * zoom_factor;
view_state.offset_y = cursor_y - (cursor_y - view_state.offset_y) * zoom_factor;
window.request_redraw();
}
MouseScrollDelta::PixelDelta(pos) => {
let zoom_delta = pos.y as f32;
if zoom_delta.abs() > 0.1 {
let zoom_factor = 1.0 + (zoom_delta * 0.005);
view_state.zoom *= zoom_factor;
view_state.zoom = view_state.zoom.max(0.1).min(10.0);
let cursor_x = view_state.last_cursor_pos.0;
let cursor_y = view_state.last_cursor_pos.1;
view_state.offset_x = cursor_x - (cursor_x - view_state.offset_x) * zoom_factor;
view_state.offset_y = cursor_y - (cursor_y - view_state.offset_y) * zoom_factor;
}
window.request_redraw();
}
}
}
}
WindowEvent::KeyboardInput { input, .. } => {
if input.state == ElementState::Pressed {
if let Some(VirtualKeyCode::Q) = input.virtual_keycode {
*control_flow = ControlFlow::Exit;
}
}
}
_ => {}
},
Event::RedrawRequested(_) => {
render(
pixels.frame_mut(),
&img_buffer,
&view_state,
window_size.width,
window_size.height,
);
if pixels.render().is_err() {
*control_flow = ControlFlow::Exit;
}
}
_ => {}
}
});
}
fn render(
frame: &mut [u8],
screenshot: &image::RgbaImage,
view_state: &ViewState,
window_width: u32,
window_height: u32,
) {
for pixel in frame.chunks_exact_mut(4) {
pixel[0] = 0;
pixel[1] = 0;
pixel[2] = 0;
pixel[3] = 255;
}
let screenshot_width = screenshot.width();
let screenshot_height = screenshot.height();
for y in 0..window_height {
for x in 0..window_width {
let src_x_f = (x as f32 - view_state.offset_x) / view_state.zoom;
let src_y_f = (y as f32 - view_state.offset_y) / view_state.zoom;
if src_x_f >= 0.0 && src_y_f >= 0.0 {
let src_x = src_x_f as u32;
let src_y = src_y_f as u32;
if src_x < screenshot_width && src_y < screenshot_height {
let pixel = screenshot.get_pixel(src_x, src_y);
let offset = (y * window_width + x) as usize * 4;
if offset + 3 < frame.len() {
frame[offset] = pixel[0];
frame[offset + 1] = pixel[1];
frame[offset + 2] = pixel[2];
frame[offset + 3] = pixel[3];
}
}
}
}
}
}

1
target/.rustc_info.json Normal file
View file

@ -0,0 +1 @@
{"rustc_fingerprint":3429588090396759731,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.91.0 (f8297e351 2025-10-28) (Homebrew)\nbinary: rustc\ncommit-hash: f8297e351a40c1439a467bbbb6879088047f50b3\ncommit-date: 2025-10-28\nhost: aarch64-apple-darwin\nrelease: 1.91.0\nLLVM version: 21.1.4\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/opt/homebrew/Cellar/rust/1.91.0\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}}

3
target/CACHEDIR.TAG Normal file
View file

@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/