3 stable releases
new 1.0.2 | Mar 3, 2025 |
---|
#128 in Graphics APIs
337 downloads per month
185KB
3.5K
SLoC
BASE-UI
개요
BASE-UI는 Rust로 작성된 OpenGL 기반의 GUI 엔진입니다. 이 프로젝트는 가볍고 사용하기 쉬운 UI 컴포넌트를 제공하여 데스크톱 애플리케이션 개발을 간소화합니다.
특징
- OpenGL 기반 렌더링: 하드웨어 가속을 통한 빠른 그래픽 렌더링
- 다양한 UI 위젯: 버튼, 텍스트 뷰, 이미지 뷰, 컨텍스트 메뉴 등 기본 UI 컴포넌트 제공
- 사용자 정의 가능한 스타일: 색상, 폰트, 크기 등을 쉽게 커스터마이징
- 이벤트 처리 시스템: 클릭, 호버 등의 사용자 상호작용 이벤트 처리
- 애니메이션 지원: UI 요소에 애니메이션 효과 적용 가능
- 텍스트 렌더링: TrueType 폰트 지원 및 고품질 텍스트 렌더링
- FIGlet 아스키 아트 지원: 콘솔 출력용 아스키 아트 생성
시작하기
요구사항
- Rust 1.70 이상
- OpenGL 3.3 이상을 지원하는 그래픽 카드 및 드라이버
설치
Cargo.toml에 다음 의존성을 추가하세요:
[dependencies]
base-ui = "1.0.2"
glutin = "0.28"
log = "0.4"
또는 이 저장소를 직접 클론하여 사용할 수 있습니다:
git clone https://github.com/yourusername/base-ui.git
cd base-ui
cargo build --release
간단한 예제
다음은 BASE-UI의 주요 기능을 보여주는 예제입니다:
// src/main.rs
//
// BASE-UI 데모 애플리케이션
// 이 파일은 BASE-UI 라이브러리의 주요 기능을 보여주는 예제입니다.
// 각 섹션은 라이브러리의 다른 기능을 시연합니다.
use base_ui::widget::widgets::context_menu::{ ContextMenu, MenuItem };
use base_ui::widget::widgets::{ Button, TextView, ImageView };
use base_ui::style::color::Color;
use glutin::event::{ ElementState, Event, MouseButton, WindowEvent };
use glutin::event_loop::ControlFlow;
use std::time::Instant;
use log::{ info, debug };
use base_ui::core::{ initialize_error_handler, Window };
use base_ui::graphics::Renderer;
use base_ui::widget::Widget;
use base_ui::widget::widgets::shape::{ Shape, ShapeType };
fn main() {
// =========================================
// 1. 초기화 및 기본 설정
// =========================================
// 오류 처리 초기화 - 패닉 발생 시 스택 트레이스 출력
initialize_error_handler();
// 윈도우 생성 (1200x1000 크기)
let (mut window, event_loop) = Window::new("BASE-UI Demo Application", 1200, 1000);
let gl_context = base_ui::initialize(&window);
info!("Window created: {}x{}", 1200, 1000);
debug!("OpenGL context initialized");
// FiraCode 폰트 로드 및 렌더러 초기화
// Path: src/assets/FiraCode-VariableFont_wght.ttf
let font_data = include_bytes!("assets/FiraCode-VariableFont_wght.ttf").to_vec();
let mut renderer = Renderer::new(font_data);
// 흰색 배경 설정
renderer.set_background_color(1.0, 1.0, 1.0, 1.0);
let mut screen_size = window.size();
// =========================================
// 2. UI 컴포넌트 생성 및 설정
// =========================================
// TextView 생성 및 스타일링
let mut label = TextView::new("Hello, Widget!", &renderer);
label.set_position(200.0, 200.0);
label.set_font_size(50.0, &renderer);
label.set_background_color(Color::new(1.0, 0.3, 0.8, 0.3)); // 반투명 핑크
label.set_text_color(Color::new(0.5, 0.4, 1.0, 1.0)); // 보라색
label.set_hover_background_color(Color::new(0.5, 0.4, 1.0, 0.3)); // 반투명 보라
label.set_hover_text_color(Color::new(1.0, 1.0, 1.0, 1.0)); // 흰색
// Button 생성 및 스타일링
let mut button = Button::new("Click Me!", &renderer);
button.set_position(300.0, 300.0);
button.set_size(200.0, 50.0);
button.set_font_size(24.0, &renderer);
button.set_background_color(Color::new(0.2, 0.6, 1.0, 1.0)); // 파란색
button.set_text_color(Color::new(1.0, 1.0, 1.0, 1.0)); // 흰색
button.set_hover_text_color(Color::new(0.9, 0.9, 0.9, 0.0)); // 연한 회색
button.set_pressed_text_color(Color::new(0.7, 0.7, 0.7, 1.0)); // 진한 회색
button.set_border_color(Color::new(0.1, 0.3, 0.8, 1.0)); // 진한 파란색
button.set_hover_border_color(Color::new(0.2, 0.4, 0.9, 1.0)); // 밝은 파란색
button.set_pressed_border_color(Color::new(0.05, 0.2, 0.6, 1.0)); // 어두운 파란색
button.set_border_width(2.0);
// ImageView 생성 및 설정
let mut image_view = ImageView::new();
image_view.set_position(500.0, 200.0);
image_view.set_size(500.0, 400.0);
// 이미지 URL에서 로드
match
image_view.load_from_url(
"https://blog.kakaocdn.net/dn/bPZNUl/btqNH1ERpNt/ytnoU8PkkkFi1Kw81jx1Y0/img.png"
)
{
Ok(_) => info!("Image loaded successfully"),
Err(e) => info!("Failed to load image: {}", e),
}
// =========================================
// 3. 컨텍스트 메뉴 설정
// =========================================
let mut context_menu = ContextMenu::new();
// 메뉴 아이템 추가 및 이벤트 핸들러 설정
let mut item1 = MenuItem::new("Open");
item1.set_on_click(|| println!("Open clicked!"));
context_menu.add_item(item1);
let mut item2 = MenuItem::new("Save");
item2.set_on_click(|| println!("Save clicked!"));
context_menu.add_item(item2);
// =========================================
// 4. 기본 도형 생성
// =========================================
// 빨간색 사각형
let mut rect = Shape::new(ShapeType::Rectangle);
rect.set_position(100.0, 100.0);
rect.set_size(200.0, 100.0);
rect.set_fill_color(Color::new(1.0, 0.0, 0.0, 1.0));
rect.set_border_color(Color::new(0.0, 0.0, 0.0, 1.0));
rect.set_border_width(2.0);
// 초록색 원
let mut circle = Shape::new(ShapeType::Circle);
circle.set_position(400.0, 100.0);
circle.set_size(100.0, 100.0);
circle.set_fill_color(Color::new(0.0, 1.0, 0.0, 1.0));
// 파란색 삼각형
let mut triangle = Shape::new(ShapeType::Triangle);
triangle.set_position(600.0, 100.0);
triangle.set_size(100.0, 100.0);
triangle.set_fill_color(Color::new(0.0, 0.0, 1.0, 1.0));
// =========================================
// 5. 이벤트 핸들러 설정
// =========================================
// 각 위젯에 개별적으로 이벤트 핸들러 설정
label.set_on_click(|| {
println!("Label clicked!");
});
label.set_on_hover(|is_hovered| {
println!("Label {}!", if is_hovered { "hovered" } else { "unhovered" });
});
button.set_on_click(|| {
println!("Button clicked!");
});
button.set_on_hover(|is_hovered| {
println!("Button {}!", if is_hovered { "hovered" } else { "unhovered" });
});
image_view.set_on_click(|| {
println!("ImageView clicked!");
});
image_view.set_on_hover(|is_hovered| {
println!("ImageView {}!", if is_hovered { "hovered" } else { "unhovered" });
});
rect.set_on_click(|| {
println!("Rectangle clicked!");
});
rect.set_on_hover(|is_hovered| {
println!("Rectangle {}!", if is_hovered { "hovered" } else { "unhovered" });
});
circle.set_on_click(|| {
println!("Circle clicked!");
});
circle.set_on_hover(|is_hovered| {
println!("Circle {}!", if is_hovered { "hovered" } else { "unhovered" });
});
triangle.set_on_click(|| {
println!("Triangle clicked!");
});
triangle.set_on_hover(|is_hovered| {
println!("Triangle {}!", if is_hovered { "hovered" } else { "unhovered" });
});
// =========================================
// 6. 애니메이션 및 이벤트 루프
// =========================================
let mut last_frame = Instant::now();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
match event {
Event::WindowEvent { event, .. } =>
match event {
WindowEvent::CloseRequested => {
info!("Application closing...");
*control_flow = ControlFlow::Exit;
}
WindowEvent::Resized(physical_size) => {
let (scale_x, scale_y) = window.resize(physical_size);
screen_size = window.size();
// 모든 위젯의 크기와 위치를 비율에 맞게 조정
label.resize_by_window_size(scale_x, scale_y);
button.resize_by_window_size(scale_x, scale_y);
image_view.resize_by_window_size(scale_x, scale_y);
rect.resize_by_window_size(scale_x, scale_y);
circle.resize_by_window_size(scale_x, scale_y);
triangle.resize_by_window_size(scale_x, scale_y);
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
window.resize(*new_inner_size);
screen_size = window.size();
}
WindowEvent::CursorMoved { position, .. } => {
window.set_cursor_position(position);
if let Some((x, y)) = window.get_cursor_position() {
button.update_hover(x as f32, y as f32);
label.update_hover(x as f32, y as f32);
image_view.update_hover(x as f32, y as f32);
// --------------------------------
context_menu.handle_mouse_move(x as f32, y as f32);
// --------------------------------
rect.update_hover(x as f32, y as f32);
circle.update_hover(x as f32, y as f32);
triangle.update_hover(x as f32, y as f32);
}
}
WindowEvent::MouseInput { state, button: MouseButton::Left, .. } => {
if let Some((x, y)) = window.get_cursor_position() {
match state {
ElementState::Pressed => {
button.on_mouse_press(x as f32, y as f32);
label.on_mouse_press(x as f32, y as f32);
image_view.on_mouse_press(x as f32, y as f32);
// --------------------------------
context_menu.handle_click(x as f32, y as f32);
// --------------------------------
rect.on_mouse_press(x as f32, y as f32);
circle.on_mouse_press(x as f32, y as f32);
triangle.on_mouse_press(x as f32, y as f32);
}
ElementState::Released => {
button.on_mouse_release(x as f32, y as f32);
label.on_mouse_release(x as f32, y as f32);
image_view.on_mouse_release(x as f32, y as f32);
// --------------------------------
context_menu.handle_click(x as f32, y as f32);
// --------------------------------
rect.on_mouse_release(x as f32, y as f32);
circle.on_mouse_release(x as f32, y as f32);
triangle.on_mouse_release(x as f32, y as f32);
}
}
}
}
WindowEvent::MouseInput {
state: ElementState::Released,
button: MouseButton::Right,
..
} => {
if let Some((x, y)) = window.get_cursor_position() {
context_menu.show(x as f32, y as f32);
}
}
_ => (),
}
Event::MainEventsCleared => {
let now = Instant::now();
let delta_time = now.duration_since(last_frame).as_secs_f32();
last_frame = now;
// 애니메이션 업데이트
label.update_animations(delta_time);
button.update_animations(delta_time);
image_view.update_animations(delta_time);
// --------------------------------
rect.update_animations(delta_time);
circle.update_animations(delta_time);
triangle.update_animations(delta_time);
// 페이드 아웃 후 페이드 인 애니메이션
if !button.has_fade_animation() {
static mut FADE_OUT: bool = true;
unsafe {
if FADE_OUT {
button.animate_fade(1.0, 0.0, 1.0); // 1.0(불투명) -> 0.0(투명)
FADE_OUT = false;
} else {
button.animate_fade(0.0, 1.0, 1.0); // 0.0(투명) -> 1.0(불투명)
FADE_OUT = true;
}
}
}
if !label.has_fade_animation() {
static mut FADE_OUT: bool = true;
unsafe {
if FADE_OUT {
label.animate_fade(1.0, 0.0, 1.0); // 1.0(불투명) -> 0.0(투명)
FADE_OUT = false;
} else {
label.animate_fade(0.0, 1.0, 1.0); // 0.0(투명) -> 1.0(불투명)
FADE_OUT = true;
}
}
}
if !rect.has_fade_animation() {
static mut FADE_OUT: bool = true;
unsafe {
if FADE_OUT {
rect.animate_fade(1.0, 0.0, 1.0); // 1.0(불투명) -> 0.0(투명)
FADE_OUT = false;
} else {
rect.animate_fade(0.0, 1.0, 1.0); // 0.0(투명) -> 1.0(불투명)
FADE_OUT = true;
}
}
}
// 사이즈 랜덤
// if loop_count % 100 == 0 {
// let random_size = rand::thread_rng().gen_range(100..300);
// label.set_size(random_size as f32, 50.0);
// button.set_size(random_size as f32, 50.0);
// image_view.set_size(random_size as f32, 200.0);
// }
gl_context.clear(0.2, 0.3, 0.3, 1.0);
// 기본 렌더링
renderer.render(screen_size.0, screen_size.1);
// 위젯 렌더링 (텍스트 포함)
label.draw(&mut renderer, screen_size.0, screen_size.1);
button.draw(&mut renderer, screen_size.0, screen_size.1);
image_view.draw(&mut renderer, screen_size.0, screen_size.1);
// 메뉴 렌더링
context_menu.draw(&mut renderer, screen_size.0, screen_size.1);
// 사각형 렌더링
rect.draw(&mut renderer, screen_size.0, screen_size.1);
// 원 렌더링
circle.draw(&mut renderer, screen_size.0, screen_size.1);
// 삼각형 렌더링
triangle.draw(&mut renderer, screen_size.0, screen_size.1);
window.swap_buffers();
}
_ => (),
}
});
}
이 예제는 다음과 같은 BASE-UI의 주요 기능을 보여줍니다:
- 윈도우 생성 및 OpenGL 컨텍스트 초기화
- 텍스트 뷰, 버튼, 이미지 뷰 등의 UI 컴포넌트 생성
- 컴포넌트 스타일링 (색상, 크기, 위치 등)
- 이벤트 처리 (클릭, 호버)
- 컨텍스트 메뉴 구현
더 자세한 예제와 고급 기능은 examples
디렉토리를 참조하세요.
컴포넌트
BASE-UI는 다음과 같은 UI 컴포넌트를 제공합니다:
- Button: 클릭 가능한 버튼
- TextView: 텍스트 표시 위젯
- ImageView: 이미지 표시 위젯
- ContextMenu: 컨텍스트 메뉴 및 메뉴 아이템
- Shape: 사각형, 원 등의 기본 도형
각 컴포넌트는 위치, 크기, 색상, 이벤트 핸들러 등을 설정할 수 있습니다.
라이선스
이 프로젝트는 MIT 라이선스 하에 배포됩니다. 자세한 내용은 LICENSE 파일을 참조하세요.
기여하기
기여는 언제나 환영합니다! 버그 리포트, 기능 요청, 풀 리퀘스트 등을 통해 프로젝트 개선에 참여해주세요.
저자
- 최시훈 - 초기 개발 및 유지보수
감사의 말
이 프로젝트는 다음 라이브러리를 사용합니다:
- gl
- glutin
- rusttype
- image
- figlet-rs
- 그 외 Cargo.toml에 명시된 의존성들
Copyright © 2025 BASE-UI GUI Engine by CHOI SIHUN, All rights reserved.
Dependencies
~21–38MB
~625K SLoC