diff --git a/res/font.ttf b/res/font.ttf new file mode 100644 index 0000000..92a90cb Binary files /dev/null and b/res/font.ttf differ diff --git a/src/button.rs b/src/button.rs new file mode 100644 index 0000000..26c20a2 --- /dev/null +++ b/src/button.rs @@ -0,0 +1,48 @@ +use sfml::{graphics::{CircleShape, Drawable, Font, Shape, Text, Transformable}, system::Vector2f}; + +use crate::constants::button::{DEFAULT_BG_COLOR, DEFAULT_TXT_COLOR, PRECISION}; + +pub struct Button<'a> { + background: CircleShape<'a>, + text: Text<'a> +} + +impl<'a> Button<'a> { + pub fn new(text: String, font: &'a Font, font_size: u32, pos: impl Into, radius: f32) -> Self { + let mut background = CircleShape::new(radius, PRECISION); + background.set_origin((radius, radius)); + background.set_position(pos); + background.set_fill_color(DEFAULT_BG_COLOR); + + let mut text = Text::new(&text, font, font_size); + text.set_position(background.position()); + text.set_fill_color(DEFAULT_TXT_COLOR); + + let bounds = text.local_bounds(); + text.set_origin(( + bounds.left + bounds.width / 2., + bounds.top + bounds.height / 2., + )); + + Button { + background, + text + } + } + + pub fn contains(&self, m_pos: Vector2f) -> bool { + self.background.global_bounds().contains(m_pos) + } +} + +impl<'d> Drawable for Button<'d> { + fn draw<'a: 'shader, 'texture, 'shader, 'shader_texture>( + &'a self, + target: &mut dyn sfml::graphics::RenderTarget, + states: &sfml::graphics::RenderStates<'texture, 'shader, 'shader_texture>, + ) { + + self.background.draw(target, states); + self.text.draw(target, states); + } +} \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..50cb56e --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,35 @@ +pub mod common { + pub const WINDOW_SIZE: (u32, u32) = (800, 600); + pub const WINDOW_SIZE_F: (f32, f32) = (WINDOW_SIZE.0 as f32, WINDOW_SIZE.1 as f32); + + pub const FPS: u32 = 60; +} + +pub mod counter { + use sfml::{graphics::Color, system::Vector2f}; + + pub const SIZE: Vector2f = Vector2f::new(200., 70.); + + pub const DEFAULT_BG_COLOR: Color = Color::YELLOW; + pub const DEFAULT_OUTLINE_COLOR: Color = Color::BLUE; + pub const DEFAULT_OUTLINE_THICKNESS: f32 = 10.; + + pub const HOFFSET_PERC: f32 = 0.35; + pub const VOFFSET_PERC: f32 = 0.30; + + pub const FONT_SIZE: u32 = 32; +} + +pub mod button { + use sfml::graphics::Color; + + pub const RADIUS_BIG: f32 = 20.; + pub const RADIUS_SMALL: f32 = 8.; + pub const PRECISION: usize = 20; + + pub const DEFAULT_BG_COLOR: Color = Color::GREEN; + pub const DEFAULT_TXT_COLOR: Color = Color::BLACK; + + pub const FONT_SIZE_BIG: u32 = 32; + pub const FONT_SIZE_SMALL: u32 = 16; +} diff --git a/src/counter.rs b/src/counter.rs new file mode 100644 index 0000000..69ab2b7 --- /dev/null +++ b/src/counter.rs @@ -0,0 +1,139 @@ +use sfml::{ + graphics::{Color, Drawable, Font, RectangleShape, Shape, Text, Transformable}, + system::Vector2f, +}; + +use crate::{button::Button, constants::{button::{FONT_SIZE_BIG, FONT_SIZE_SMALL, RADIUS_BIG, RADIUS_SMALL}, common::WINDOW_SIZE_F, counter::{DEFAULT_BG_COLOR, DEFAULT_OUTLINE_COLOR, DEFAULT_OUTLINE_THICKNESS, FONT_SIZE, HOFFSET_PERC, SIZE, VOFFSET_PERC}}}; + +pub struct Counter<'a> { + count: u32, + count_str: Text<'a>, + background: RectangleShape<'a>, + dec_button: Button<'a>, + inc_button: Button<'a>, + reset_button: Button<'a>, +} + +impl<'a> Counter<'a> { + pub fn new(count: u32, font: &'a Font) -> Self { + let mut background = RectangleShape::with_size(SIZE); + background.set_fill_color(DEFAULT_BG_COLOR); + background.set_outline_color(DEFAULT_OUTLINE_COLOR); + background.set_outline_thickness(DEFAULT_OUTLINE_THICKNESS); + background.set_position((WINDOW_SIZE_F.0/2., WINDOW_SIZE_F.1/2.)); + let bounds = background.local_bounds(); + background.set_origin(( + bounds.left + bounds.width / 2., + bounds.top + bounds.height / 2., + )); + + let mut count_str = Text::new(&count.to_string(), font, FONT_SIZE); + count_str.set_fill_color(Color::BLACK); + count_str.set_position((WINDOW_SIZE_F.0/2., WINDOW_SIZE_F.1/2.)); + let bounds = count_str.local_bounds(); + count_str.set_origin(( + bounds.left + bounds.width / 2., + bounds.top + bounds.height / 2., + )); + + let dec_button = Button::new( + "-".to_string(), + font, + FONT_SIZE_BIG, + Vector2f::new( + background.position().x - HOFFSET_PERC*background.size().x, + background.position().y, + ), + RADIUS_BIG + ); + + let inc_button = Button::new( + "+".to_string(), + font, + FONT_SIZE_BIG, + Vector2f::new( + background.position().x + HOFFSET_PERC*background.size().x, + background.position().y, + ), + RADIUS_BIG + ); + + let reset_button = Button::new( + "R".to_string(), + font, + FONT_SIZE_SMALL, + Vector2f::new( + background.position().x, + background.position().y + (1. - VOFFSET_PERC)*background.size().y/2. + ), + RADIUS_SMALL + ); + + Counter { + count, + count_str, + background, + dec_button, + inc_button, + reset_button, + } + } + + pub fn inc(&mut self) { + if self.count < u32::MAX { + self.count += 1; + } + self.update(); + } + + pub fn dec(&mut self) { + if self.count > 0 { + self.count -= 1; + } + self.update(); + } + + pub fn reset(&mut self) { + self.count = 0; + self.update(); + } + + fn update(&mut self) { + self.count_str.set_string(&self.count.to_string()); + let digits = self.count.checked_ilog10().unwrap_or(0); + if self.count % (10 * if digits == 0 { 1 } else { digits }) == 0 { + let bounds = self.count_str.local_bounds(); + self.count_str.set_origin(( + bounds.left + bounds.width / 2., + bounds.top + bounds.height / 2., + )); + } + } + + pub fn handle_mouse(&mut self, m_pos: impl Into) { + let m_pos = m_pos.into(); + if self.dec_button.contains(m_pos) { + self.dec(); + } + if self.inc_button.contains(m_pos) { + self.inc(); + } + if self.reset_button.contains(m_pos) { + self.reset(); + } + } +} + +impl<'d> Drawable for Counter<'d> { + fn draw<'a: 'shader, 'texture, 'shader, 'shader_texture>( + &'a self, + target: &mut dyn sfml::graphics::RenderTarget, + states: &sfml::graphics::RenderStates<'texture, 'shader, 'shader_texture>, + ) { + self.background.draw(target, states); + self.count_str.draw(target, states); + self.dec_button.draw(target, states); + self.inc_button.draw(target, states); + self.reset_button.draw(target, states); + } +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..a2d0fd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,41 @@ -fn main() { - println!("Hello, world!"); +use sfml::{graphics::{Color, Font, RenderTarget, RenderWindow}, window::{mouse, Event, Key, Style}, SfResult}; + +mod counter; +mod button; +mod constants; + +use counter::Counter; +use crate::constants::common::{FPS, WINDOW_SIZE}; + + +fn main() -> SfResult<()>{ + let font = Font::from_file("res/font.ttf").unwrap(); + let mut c1 = Counter::new(0, &font); + + let mut window = RenderWindow::new(WINDOW_SIZE, "Multi Counter", Style::CLOSE, &Default::default())?; + window.set_framerate_limit(FPS); + + while window.is_open() { + while let Some(event) = window.poll_event() { + match event { + Event::Closed => window.close(), + Event::KeyPressed { code, .. } => match code { + Key::Left => c1.dec(), + Key::Right => c1.inc(), + Key::Space => c1.reset(), + _ => {} + }, + Event::MouseButtonPressed { button, x, y } => if button == mouse::Button::Left { + c1.handle_mouse((x as f32, y as f32)); + } + _ => {} + } + } + + window.clear(Color::BLACK); + window.draw(&c1); + window.display(); + } + + Ok(()) }