๐ŸŽ›๏ธ :: ๋‹ค์ค‘ ์œˆ๋„์šฐ - ์ƒ‰ ๋ฐ”๊พธ๊ธฐ & ๋ชจ์–‘ ๋ฐ”๊พธ๊ธฐ

BamgasiJMยท2025๋…„ 9์›” 19์ผ

Nannou <BASIC>

๋ชฉ๋ก ๋ณด๊ธฐ
5/41
post-thumbnail

๐Ÿ“ main() ํ•จ์ˆ˜์—์„œ ์ฐฝ ์„ค์ •ํ•˜๊ธฐ

fn main() {
  nannou::app(model)
  		 .update(update)
  		 .simple_window(view)
  		 .size(800, 800)
  		 .run();
}

simple_window๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ App::new_window()๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ๊ทธ WindowBuilder์— .size()๋ฅผ ์ด์–ด ๋ถ™์ด๋Š” ๋ฐฉ์‹

  • ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ์“ธ ์ˆ˜ ์žˆ์–ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์— ์ ํ•ฉ.
  • ์„ธ๋ถ€ ์˜ต์…˜์„ ๋ถ™์ด๋ฉด ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง.
  • ํ•˜๋‚˜์˜ ์ฐฝ๋งŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Œ.

๐Ÿ“ model() ํ•จ์ˆ˜์—์„œ ์ฐฝ ์„ค์ •ํ•˜๊ธฐ

fn model(app: &App) -> Model {
  app.new_window()
  	 .size(800, 800)
  	 .view(view)
  	 .build()
  	 .unwrap();
 
  Model {}
}

fn model()์€ App๊ฐ์ฒด๋ฅผ ์ „๋‹ฌ๋ฐ›์œผ๋ฏ€๋กœ ์ด ์•ˆ์—์„œ ์ƒˆ ์ฐฝ์„ ๋งŒ๋“œ๋Š” ๋ฐฉ์‹
์ด ๊ฒฝ์šฐ main()์—๋Š” nannou::app(model).update(update).run();๋งŒ ์ž‘์„ฑ.

  • ์ฐฝ ์†์„ฑ(์ œ๋ชฉ, ์œ„์น˜, vsync, event ์ฒ˜๋ฆฌ ๋“ฑ)์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ด ๊ฐ€๋Šฅ.
  • ์—ฌ๋Ÿฌ ์ฐฝ ์ƒ์„ฑ ๊ฐ€๋Šฅ -> model()์—์„œ ์›ํ•˜๋Š” ๋งŒํผ new_window() ํ˜ธ์ถœ.
  • ์ฐฝ๋งˆ๋‹ค ๋‹ค๋ฅธ view()ํ•จ์ˆ˜, ๋‹ค๋ฅธ ํฌ๊ธฐ, ๋‹ค๋ฅธ ์ด๋ฆ„์„ ์ง€์ • ๊ฐ€๋Šฅ.
  • "์•ฑ ์ƒํƒœ(Model) + ์ฐฝ(Window)"๋กœ ๋ถ„๋ฆฌ๋˜์–ด Model๊ณผ ํ•จ๊ป˜ ์ฐฝ ID๋ฅผ ์ €์žฅํ•ด๋‘๊ณ  ์ดํ›„์— ํŠน์ • ์ฐฝ์„ ์ฐพ์•„์„œ update()๋‚˜ view()์—์„œ ํ™œ์šฉ.

model()์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฐฝ ์„ค์ • ๋ฉ”์„œ๋“œ (WindowBuilder)

app.new_window()๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฑด nannou::app::window::Builderํƒ€์ž…์ธ๋ฐ, ์—ฌ๊ธฐ์„œ ์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ๋ถ™์ผ ์ˆ˜ ์žˆ์Œ.
https://docs.rs/nannou/latest/nannou/window/struct.Builder.html

์ฃผ์š” ๋ฉ”์„œ๋“œ์„ค๋ช…
.size(w, h)์ฐฝ์˜ ํฌ๊ธฐ๋ฅผ ์ง€์ • (ํ”ฝ์…€ ๋‹จ์œ„)
.title("string")์ฐฝ ์ œ๋ชฉ ์„ค์ •
.resizable(bool)์ฐฝ ํฌ๊ธฐ ์กฐ์ ˆ ๊ฐ€๋Šฅ ์—ฌ๋ถ€
.fullscreen()์ „์ฒด ํ™”๋ฉด ๋ชจ๋“œ
.decorations(bool)์ฐฝ์˜ ํƒ€์ดํ‹€๋ฐ”, ๋‹ซ๊ธฐ ๋ฒ„ํŠผ ๋“ฑ ํ‘œ์‹œ ์—ฌ๋ถ€
.always_on_top(bool)์ฐฝ์„ ํ•ญ์ƒ ์œ„์— ๋‘๊ธฐ
.position(x, y)์ฐฝ์˜ ์ดˆ๊ธฐ ์œ„์น˜ ์ง€์ • (ํ™”๋ฉด ์ขŒํ‘œ)
.view(view_fn)๋ Œ๋”๋งํ•  view ํ•จ์ˆ˜ ๋“ฑ๋ก
.event(event_fn)ํ‚ค๋ณด๋“œ, ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
.key_pressed(key_fn)ํŠน์ • ํ‚ค ์ž…๋ ฅ ์ฒ˜๋ฆฌ
.mouse_pressed(mouse_fn)๋งˆ์šฐ์Šค ์ž…๋ ฅ ์ฒ˜๋ฆฌ
.raw_event(raw_event_fn)๋” ์ €์ˆ˜์ค€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
.update(update_fn)์ด ์ฐฝ ์ „์šฉ update ํ•จ์ˆ˜ ์ง€์ • (์ „์—ญ update์™€ ๋ณ„๋„๋กœ)
.vsync(bool)V-Sync ํ™œ์„ฑ/๋น„ํ™œ์„ฑ
.samples(n)MSAA ์ƒ˜ํ”Œ ์ˆ˜ (์•ˆํ‹ฐ์•จ๋ฆฌ์–ด์‹ฑ ํ’ˆ์งˆ)
.srgb(bool)sRGB ์ƒ‰๊ณต๊ฐ„ ํ™œ์„ฑํ™”
.transparent(bool)์ฐฝ ๋ฐฐ๊ฒฝ์„ ํˆฌ๋ช…ํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ

๐Ÿ“ ์˜ˆ์‹œ ์ฝ”๋“œ (์ƒ‰ ๋ฐ”๊พธ๊ธฐ)

use nannou::prelude::*;
use nannou::rand::prelude::*;

fn main() {
    nannou::app(model).update(update).event(event).run();
}

struct Model {
    win_sub: WindowId,
    current_color: Rgb8,
    current_color_name: String,
}

// nannou color ๋ชจ๋“ˆ์˜ ๋ชจ๋“  ์ƒ‰์ƒ ์ƒ์ˆ˜๋“ค (์ด 147๊ฐœ ์ค‘ ์ผ๋ถ€)
const COLORS: &[(Rgb8, &str)] = &[
    (ALICEBLUE, "ALICEBLUE"),
    (ANTIQUEWHITE, "ANTIQUEWHITE"),
    (AQUA, "AQUA"),
    (AQUAMARINE, "AQUAMARINE"),
    (AZURE, "AZURE"),
    (BEIGE, "BEIGE"),
    (BISQUE, "BISQUE"),
    (WHITESMOKE, "WHITESMOKE"),
    (YELLOW, "YELLOW"),
    (YELLOWGREEN, "YELLOWGREEN"),
];

fn model(app: &App) -> Model {
    // ์ฒซ ๋ฒˆ์งธ ์ฐฝ (๋ฉ”์ธ)
    let _win_main = app
        .new_window()
        .size(600, 400)
        .title("Main Window")
        .view(view_main)
        .build()
        .unwrap();

    // ๋‘ ๋ฒˆ์งธ ์ฐฝ (์„œ๋ธŒ)
    let win_sub = app
        .new_window()
        .size(400, 400)
        .title("Sub Window")
        .view(view_sub)
        .build()
        .unwrap();

    Model {
        win_sub,
        current_color: WHITE,
        current_color_name: "WHITE".to_string(),
    }
}

fn update(_app: &App, _model: &mut Model, _update: Update) {}

fn event(_app: &App, model: &mut Model, event: Event) {
    match event {
        Event::WindowEvent { id, simple: Some(MousePressed(_button)), .. } => {
            // sub ์ฐฝ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ๋งŒ ์ƒ‰์ƒ ๋ณ€๊ฒฝ
            if id == model.win_sub {
                let mut rng = thread_rng();
                let (color, color_name) = COLORS.choose(&mut rng).unwrap();
                model.current_color = *color;
                model.current_color_name = color_name.to_string();
            }
        }
        _ => {}
    }
}

fn view_main(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background().color(model.current_color);

    draw.text(&model.current_color_name).color(BLACK).font_size(24).x_y(0.0, 0.0);

    draw.to_frame(app, &frame).unwrap();
}

fn view_sub(app: &App, _model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background().color(GRAY);

    draw.text("CLICK TO CHANGE THE COLOR").color(BLACK).font_size(18).x_y(0.0, 0.0);

    draw.to_frame(app, &frame).unwrap();
}

event() ํ•จ์ˆ˜์˜ ๋™์ž‘ ๊ณผ์ •

ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜

fn event(_app: &App, model: &mut Model, event: Event)
  • _app: ์•ฑ ์ฐธ์กฐ (์–ธ๋”์Šค์ฝ”์–ด๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์„ ํ‘œ์‹œ)
  • model: ๋ชจ๋ธ์˜ ๊ฐ€๋ณ€ ์ฐธ์กฐ (๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Œ)
  • event: ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ ์ •๋ณด

match ๋ฌธ๋ฒ•

match event {
    // ํŒจํ„ด๋“ค...
}
  • match๋Š” Rust์˜ ํŒจํ„ด ๋งค์นญ ๋ฌธ๋ฒ•์ž…๋‹ˆ๋‹ค. event์˜ ๊ฐ’์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • C์–ธ์–ด์˜ switch๋ฌธ๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ ๋” ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ํŒจํ„ด: WindowEvent

Event::WindowEvent { id, simple: Some(MousePressed(_button)), .. } => {
    // ์‹คํ–‰๋  ์ฝ”๋“œ
}

์ด ํŒจํ„ด์„ ๋ถ„ํ•ดํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค:
Event::WindowEvent

  • Event๋Š” ์—ด๊ฑฐํ˜•(enum)์ด๊ณ , WindowEvent๋Š” ๊ทธ ์ค‘ ํ•˜๋‚˜์˜ ๋ณ€ํ˜•์ž…๋‹ˆ๋‹ค
  • ์ฐฝ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ(ํด๋ฆญ, ํ‚ค๋ณด๋“œ ์ž…๋ ฅ ๋“ฑ)๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค

{ id, simple: Some(MousePressed(_button)), .. }
์ด๋Š” ๊ตฌ์กฐ์ฒด ํŒจํ„ด ๋งค์นญ์ž…๋‹ˆ๋‹ค:

  • id: ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ์ฐฝ์˜ ID๋ฅผ id ๋ณ€์ˆ˜์— ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค

  • simple: Some(MousePressed(_button)):

    • simple์€ ๊ฐ„๋‹จํ•œ ์ด๋ฒคํŠธ ์ •๋ณด๋ฅผ ๋‹ด๋Š” ํ•„๋“œ์ž…๋‹ˆ๋‹ค
    • Some(...)์€ Option ํƒ€์ž…์˜ ๊ฐ’์ด ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค
    • MousePressed(_button)์€ ๋งˆ์šฐ์Šค๊ฐ€ ๋ˆŒ๋ ธ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค
    • _button์€ ์–ด๋–ค ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ์ธ์ง€๋Š” ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค (์–ธ๋”์Šค์ฝ”์–ด)
  • ..: ๋‚˜๋จธ์ง€ ํ•„๋“œ๋“ค์€ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค

์กฐ๊ฑด๋ฌธ๊ณผ ์ƒ‰์ƒ ๋ณ€๊ฒฝ

if id == model.win_sub {
    let mut rng = thread_rng();
    let (color, color_name) = COLORS.choose(&mut rng).unwrap();
    model.current_color = *color;
    model.current_color_name = color_name.to_string();
}
  • if id == model.win_sub: ํด๋ฆญ๋œ ์ฐฝ์ด ์„œ๋ธŒ ์ฐฝ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
  • let mut rng = thread_rng(): ๋‚œ์ˆ˜ ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค
  • COLORS.choose(&mut rng).unwrap(): ์ƒ‰์ƒ ๋ฐฐ์—ด์—์„œ ๋žœ๋คํ•˜๊ฒŒ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค
  • let (color, color_name) = ...: ํŠœํ”Œ ๊ตฌ์กฐ๋ถ„ํ•ด๋กœ ์ƒ‰์ƒ๊ณผ ์ด๋ฆ„์„ ๊ฐ๊ฐ ๋ณ€์ˆ˜์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค
  • *color: ์ƒ‰์ƒ ๊ฐ’์„ ์—ญ์ฐธ์กฐํ•ด์„œ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค
  • color_name.to_string(): ๋ฌธ์ž์—ด ์Šฌ๋ผ์ด์Šค๋ฅผ String์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค

๋‘ ๋ฒˆ์งธ ํŒจํ„ด: ๊ธฐ๋ณธ ์ผ€์ด์Šค

_ => {}
  • _๋Š” ์™€์ผ๋“œ์นด๋“œ ํŒจํ„ด์œผ๋กœ "๋‚˜๋จธ์ง€ ๋ชจ๋“  ๊ฒฝ์šฐ"๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค
  • {}๋Š” ๋นˆ ๋ธ”๋ก์œผ๋กœ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • ์ฆ‰, WindowEvent์˜ ๋งˆ์šฐ์Šค ํด๋ฆญ์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๋ชจ๋“  ์ด๋ฒคํŠธ๋Š” ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค

์ „์ฒด ๋™์ž‘ ํ๋ฆ„

  1. ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด event ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค
  2. match๋ฌธ์œผ๋กœ ์ด๋ฒคํŠธ ํƒ€์ž…์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
  3. ์ฐฝ์—์„œ ๋งˆ์šฐ์Šค๊ฐ€ ํด๋ฆญ๋œ ๊ฒฝ์šฐ:
  • ํด๋ฆญ๋œ ์ฐฝ์ด ์„œ๋ธŒ ์ฐฝ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
  • ๋งž๋‹ค๋ฉด ๋žœ๋ค ์ƒ‰์ƒ์„ ์„ ํƒํ•ด์„œ ๋ชจ๋ธ์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค
  1. ๋‹ค๋ฅธ ๋ชจ๋“  ์ด๋ฒคํŠธ๋Š” ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค

์ด๋ ‡๊ฒŒ ํ•ด์„œ ์„œ๋ธŒ ์ฐฝ์„ ํด๋ฆญํ•  ๋•Œ๋งŒ ๋ฉ”์ธ ์ฐฝ์˜ ์ƒ‰์ƒ์ด ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ“ ์˜ˆ์‹œ ์ฝ”๋“œ (๋ชจ์–‘ ๋ฐ”๊พธ๊ธฐ)

use nannou::prelude::*;

struct Model {
    main_window: WindowId,
    control_window: WindowId,
    target_sides: usize, // ํ˜„์žฌ ๋‹ค๊ฐํ˜• ๋ณ€ ๊ฐœ์ˆ˜
}

// ๋ฒ„ํŠผ ์ •์˜
struct Button {
    rect: Rect,
    label: &'static str,
}

impl Button {
    fn draw(&self, draw: &Draw) {
        draw.rect()
            .xy(self.rect.xy())
            .wh(self.rect.wh())
            .color(STEELBLUE);
        draw.text(self.label)
            .xy(self.rect.xy())
            .color(WHITE)
            .font_size(18);
    }

    fn contains(&self, point: Point2) -> bool {
        self.rect.contains(point)
    }
}

fn main() {
    nannou::app(model).update(update).run();
}

fn model(app: &App) -> Model {
    let main_window = app
        .new_window()
        .title("Main Window")
        .size(600, 600)
        .view(view_main)
        .build()
        .unwrap();

    let control_window = app
        .new_window()
        .title("Control Window")
        .size(300, 200)
        .view(view_control)
        .event(event_control)
        .build()
        .unwrap();

    Model {
        main_window,
        control_window,
        target_sides: 6,
    }
}

fn update(_app: &App, _model: &mut Model, _update: Update) {
    // ์ง€๊ธˆ์€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์—†์Œ โ†’ ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•จ
}

// === ๋ฉ”์ธ ์œˆ๋„์šฐ ===
fn view_main(app: &App, model: &Model, frame: Frame) {
    if frame.window_id() == model.main_window {
        let draw = app.draw();
        draw.background().color(rgba(0.2, 0.2, 0.2, 1.0));

        // ๋ฉ”์ธ ๋„ํ˜• ๊ทธ๋ฆฌ๊ธฐ
        let polygon = (0..model.target_sides).map(|i| {
            let angle = i as f32 / model.target_sides as f32 * TAU;
            pt2(angle.cos() * 200.0, angle.sin() * 200.0)
        });
        draw.polygon().points(polygon).color(DARKTURQUOISE);

        // ๋‹ค๊ฐํ˜• ๋ณ€ ๊ฐœ์ˆ˜๋ฅผ ์ค‘์•™์— ํ‘œ์‹œ
        draw.text(&model.target_sides.to_string())
            .x_y(0.0, 0.0)
            .color(WHITE)
            .font_size(64);

        draw.to_frame(app, &frame).unwrap();
    }
}


// === ์ปจํŠธ๋กค ์œˆ๋„์šฐ ===
fn view_control(app: &App, _model: &Model, frame: Frame) {
    let draw = app.draw();
    draw.background().color(BLACK);

    // ๋ฒ„ํŠผ ๋‘ ๊ฐœ
    let inc_button = Button {
        rect: Rect::from_xy_wh(pt2(-80.0, 0.0), vec2(100.0, 50.0)),
        label: "+",
    };
    let dec_button = Button {
        rect: Rect::from_xy_wh(pt2(80.0, 0.0), vec2(100.0, 50.0)),
        label: "-",
    };

    inc_button.draw(&draw);
    dec_button.draw(&draw);

    draw.to_frame(app, &frame).unwrap();
}

fn event_control(app: &App, model: &mut Model, event: WindowEvent) {
    // ํ˜„์žฌ ์ด๋ฒคํŠธ๊ฐ€ ์–ด๋А ์œˆ๋„์šฐ์—์„œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ํ™•์ธ
    if app.window_id() == model.control_window {
        match event {
            WindowEvent::MousePressed(_button) => {
                let pos = app.mouse.position();

                let inc_button = Button {
                    rect: Rect::from_xy_wh(pt2(-80.0, 0.0), vec2(100.0, 50.0)),
                    label: "+",
                };
                let dec_button = Button {
                    rect: Rect::from_xy_wh(pt2(80.0, 0.0), vec2(100.0, 50.0)),
                    label: "-",
                };

                if inc_button.contains(pos) {
                    model.target_sides += 1;
                } else if dec_button.contains(pos) {
                    if model.target_sides > 3 {
                        model.target_sides -= 1;
                    }
                }
            }
            _ => {}
        }
    }
}

โœ… โš™๏ธ๐Ÿ› ๏ธ๐Ÿ“„๐Ÿ“š๐ŸŽจ๐Ÿ–ผ๏ธ๐Ÿงฉ๐Ÿง ๐Ÿงฎ๐Ÿ•น๏ธ๐Ÿงฟโณ๐ŸŽ›๏ธ๐Ÿ”ฎ๐ŸŒŒ๐Ÿชฉ๐Ÿ—‚๏ธ๐Ÿ“๐Ÿงฐ๐Ÿ”’

์•„๋ณด์นด๋„
Sample
Important
Highlighting
๋น„์ƒ‰
์ ํ™ฉ์ƒ‰
purple
hot pink
yellow
์‚ด๊ตฌ์ƒ‰
orange
green
blue

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€