.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
4148
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
8
Cargo.toml
Normal file
8
Cargo.toml
Normal 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
253
src/main.rs
Normal 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()))),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user