This commit is contained in:
Numbers
2025-11-13 09:43:30 -05:00
commit c8ef8982ac
5 changed files with 4418 additions and 0 deletions

1
.gitignore vendored Normal file
View File

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

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

4148
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

8
Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "depixel"
version = "0.1.0"
edition = "2024"
[dependencies]
eframe = "0.33.0"
egui = "*"

253
src/main.rs Normal file
View File

@@ -0,0 +1,253 @@
use eframe::egui;
use egui::{Align2, Color32, Pos2, Rect, Response, Sense, Stroke, Vec2, Widget};
#[derive(Clone, Copy, Debug)]
pub struct Corner {
pub position: Pos2,
pub dragging: bool,
}
impl Corner {
pub fn new(position: Pos2) -> Self {
Self {
position,
dragging: false,
}
}
}
pub struct BillboardProjector {
pub corners: [Corner; 4], // Top-left, top-right, bottom-right, bottom-left
pub text: String,
pub image_size: Vec2,
pub corner_radius: f32,
active_corner: Option<usize>,
}
impl BillboardProjector {
pub fn new(image_size: Vec2) -> Self {
// Initialize corners in a reasonable default rectangle
let margin = 50.0;
Self {
corners: [
Corner::new(Pos2::new(margin, margin)),
Corner::new(Pos2::new(image_size.x - margin, margin)),
Corner::new(Pos2::new(image_size.x - margin, image_size.y - margin)),
Corner::new(Pos2::new(margin, image_size.y - margin)),
],
text: "Sample Text".to_string(),
image_size,
corner_radius: 8.0,
active_corner: None,
}
}
pub fn set_text(&mut self, text: String) {
self.text = text;
}
fn draw_corner(&self, ui: &mut egui::Ui, corner: &Corner, index: usize, corner_rect: Rect) -> bool {
let is_active = self.active_corner == Some(index);
let color = if is_active {
Color32::RED
} else {
Color32::BLUE
};
ui.painter().circle_filled(
corner.position,
self.corner_radius,
color,
);
ui.painter().circle_stroke(
corner.position,
self.corner_radius,
Stroke::new(2.0, Color32::WHITE),
);
corner_rect.contains(ui.input(|i| i.pointer.interact_pos().unwrap_or_default()))
}
fn draw_billboard_outline(&self, ui: &mut egui::Ui) {
let painter = ui.painter();
// Draw lines between corners
for i in 0..4 {
let start = self.corners[i].position;
let end = self.corners[(i + 1) % 4].position;
painter.line_segment(
[start, end],
Stroke::new(2.0, Color32::YELLOW),
);
}
// Fill the quad with semi-transparent overlay
let points = self.corners.iter().map(|c| c.position).collect::<Vec<_>>();
if points.len() == 4 {
painter.add(egui::Shape::convex_polygon(
points,
Color32::from_rgba_unmultiplied(255, 255, 0, 30),
Stroke::new(1.0, Color32::TRANSPARENT),
));
}
}
fn project_text_onto_billboard(&self, ui: &mut egui::Ui) {
// Simple text projection - place text in the center of the quadrilateral
let center = self.get_quad_center();
// Calculate approximate scale based on quad size
let quad_width = (self.corners[1].position - self.corners[0].position).length();
let quad_height = (self.corners[3].position - self.corners[0].position).length();
let scale = (quad_width + quad_height) * 0.0008; // Adjust this multiplier as needed
// Calculate rotation based on top edge angle
let top_edge = self.corners[1].position - self.corners[0].position;
let angle = top_edge.y.atan2(top_edge.x);
ui.painter().text(
center,
egui::Align2::CENTER_CENTER,
&self.text,
egui::FontId::proportional(scale * 50.0), // Base font size
Color32::WHITE,
);
// Draw text shadow for better visibility
ui.painter().text(
center + Vec2::new(2.0, 2.0),
egui::Align2::CENTER_CENTER,
&self.text,
egui::FontId::proportional(scale * 50.0),
Color32::BLACK,
);
}
fn get_quad_center(&self) -> Pos2 {
let sum = self.corners.iter().fold(Vec2::ZERO, |acc, corner| {
acc + corner.position.to_vec2()
});
(sum / 4.0).to_pos2()
}
}
impl Widget for &mut BillboardProjector {
fn ui(self, ui: &mut egui::Ui) -> Response {
let (rect, response) = ui.allocate_exact_size(self.image_size, Sense::click_and_drag());
if ui.is_rect_visible(rect) {
// Handle corner dragging
let pointer_pos = ui.input(|i| i.pointer.interact_pos());
let is_dragging = ui.input(|i| i.pointer.primary_down());
if let Some(pos) = pointer_pos {
let relative_pos = pos.to_vec2();
if is_dragging {
if self.active_corner.is_none() {
// Check which corner to start dragging
for (i, corner) in self.corners.iter().enumerate() {
let corner_rect = Rect::from_center_size(
corner.position,
Vec2::splat(self.corner_radius * 2.0),
);
if corner_rect.contains(relative_pos.to_pos2()) {
self.active_corner = Some(i);
break;
}
}
}
// Update active corner position
if let Some(active_idx) = self.active_corner {
self.corners[active_idx].position = relative_pos.clamp(
Vec2::ZERO,
self.image_size,
).to_pos2();
}
} else {
self.active_corner = None;
}
}
// Draw billboard outline and fill
self.draw_billboard_outline(ui);
// Project text onto billboard
self.project_text_onto_billboard(ui);
// Draw corners (on top)
for (i, corner) in self.corners.iter().enumerate() {
let corner_rect = Rect::from_center_size(
corner.position,
Vec2::splat(self.corner_radius * 2.0),
);
self.draw_corner(ui, corner, i, corner_rect);
}
}
response
}
}
// Example usage in your main app
pub struct MyApp {
billboard_projector: BillboardProjector,
}
impl MyApp {
pub fn new() -> Self {
Self {
billboard_projector: BillboardProjector::new(Vec2::new(800.0, 600.0)),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Billboard Text Projector");
// Text input
ui.horizontal(|ui| {
ui.label("Text to project:");
let mut text = self.billboard_projector.text.clone();
if ui.text_edit_singleline(&mut text).changed() {
self.billboard_projector.set_text(text);
}
});
ui.separator();
// Instructions
ui.label("Click and drag the blue/red circles to adjust billboard corners");
ui.label("The yellow quadrilateral shows the billboard area");
ui.separator();
// Billboard projector widget
ui.add(&mut self.billboard_projector);
// Corner coordinates display
ui.collapsing("Corner Coordinates", |ui| {
for (i, corner) in self.billboard_projector.corners.iter().enumerate() {
ui.label(format!("Corner {}: ({:.1}, {:.1})",
i + 1, corner.position.x, corner.position.y));
}
});
});
}
}
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([1024.0, 768.0]),
..Default::default()
};
eframe::run_native(
"Billboard Text Projector",
options,
Box::new(|_cc| Ok(Box::new(MyApp::new()))),
)
}