.
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