오래간만의 슬로그네요.
Duxel은 앞전에 MewUI의 동작성에 자극을 받아 평소에 만들고 싶었던 Dear ImGui 스타일의 GUI 라이브러리입니다. 관련된 정보는 아래의 링크를 통해 확인할 수 있어요.
안녕하세요 .앞 전에 공유 드렸던 라이브러리를
좀 더 개선해서 버전 0.1.5-preview를 출시했습니다.
깃허브 저장소는 다음과 같습니다.
Duxel은 Dear ImGui 와 유사한 즉시 렌더링 모델을 GPT-5.2-Codex를 이용해 .NET 10용으로 구현했습니다. GUI를 구성하는 별도의 논리적 트리가 없으므로 구조가 단순하며, GPU를 활용해 화면을 빠르게 다시 렌더링하는 데 집중합니다.
최신 버전은 v0.1.5-preview로, 알파 단계에 있어 제품에 사용할 만큼 안정적이지는 않지만 대부분의 기능이 정상적으로 동작합니다.
주요 특징으로는 다음과 같습니다.
즉시 모드 UI — Dear ImGui 스타일의 Begin/End 패턴 기반 위젯 API
Vulkan 렌더러 — MSAA 4x, VSync 토글, Triple Buffering, Persistent Mapped Buffers
GLFW 윈도우/입력 — 키보드·마우스·스크롤·IME 입력 지원
스크롤바…
@al6uiz 님의 MewUI 에 자극을 받아서 평소에 만들고 싶었던 고속 GUI 라이브러리를 바이브 코딩으로 만들고 있습니다.
dotnet의 FBA 덕분에 .NET 10이 설치되어 있다면 아래 한줄로 실행해 볼 수 있습니다.
curl -sL https://raw.githubusercontent.com/dimohy/Duxel/refs/heads/main/samples/fba/Duxel_perf_test_fba.cs -o - | dotnet run -
실행하고 Add 버튼을 눌러서 동작을 확인할 수 있어요!
[image]
.
100% 바이브 코딩으로 진행하고 있으며 GPT-5.2-Codex → GPT-5.3-Codex 모델로 GitHub Copilot을 이용해 만들고 있습니다.
슬로그를 통해 달성하고 싶은 계획은 다음과 같아요.
좀 더 사용하기 편한 구조로 변환 (취소)
Duxel은 별도의 렌더 트리가 없어요. 그래서 WinForms나 WPF에 익숙한 분께는 거칠어요. 그냥 OnRender 메소드에서 일일이 화면에 그려서 구성하는 방법이기 때문이에요.
그래서 Fluent API를 이용해서 선언적인 구조로 변경하려고 했습니다만…
꽤 긴 삽질을 한 이후에 이런 디자인이 ImGui 스타일과 맞지 않다는 결론에 도달했습니다. 그래서 취소
→ 이것보다 DSL 문법을 적극 활용하는 방법으로 고도화 하려고 합니다.
모든 위젯의 동작성 확보
대부분의 위젯이 잘 동작하지만 위치가 이상하거나 오동작 하는 위젯이 상당히 있습니다. 이를 보정하는 작업입니다.
깔끔한 글자 출력 및 속도 보장
Vulkan을 직접 다루다 보니 DirectWrite 수준의 글자를 출력하기가 정말로 어렵더라고요… 관련 전문 지식도 없었던지라 엄청난 삽질 이후 DirectWrite를 추가하게 되어 구조적인 문제가 현제는 있습니다. 관련해서 Chromium 수준의 폰트 렌더링을 확보하는 것입니다.
그 다음은 크로스플랫폼
Duxel은 원래 크로스플랫폼 용 라이브러리를 위해 시작하였어요. 그런데 아직은 Windows에서만 동작합니다. 리눅스 및 iOS에서 동작하도록 확장할 예정이에요.
Silk.NET의 의존성 제거
Vulkan의 기능을 이용하기 위해 Silk.NET을 썼지만 알기로는 NativeAOT와 친화적이라고 알고 있었는데 Trim이 안되는 사소한 문제가 있어서 최종 실행 파일 크기가 4MB를 넘는 문제가 있습니다. Silk.NET를 걷어내서 3M 미만으로 실행파일을 줄이는 것이 목표입니다.
결국엔 뭘 하고 싶은거지?
MewUI가 Window Forms 및 WPF의 굴레에서 벗어나 크로스플랫폼으로 NativeAOT의 장점들을 활용할 수 있게 된 것 처럼
Duexl은 고속/실시간 2D 렌더링이 필요한 다양한 목적에 활용할 수 있도록 발전할 예정입니다.
그런데 바이브코딩을 해도 상당한 시간과 노력이 들어가는 것은 당황스럽네요 ^^;
8개의 좋아요
먼저 브랜딩 부터…
[ Duxel ] — Immediate GUI for .NET
^^;
4개의 좋아요
0.1.15-preview
주요 기능 추가
플랫폼 텍스트 백엔드 추상화 : IPlatformTextBackend 인터페이스로 아틀라스 파이프라인과 분리된 크로스 플랫폼 텍스트 래스터라이제이션 지원.
DWrite 백엔드 추가 : WindowsPlatformTextBackend가 텍스트-런 단위로 COM 호출 → 글리프별 호출 대비 성능 개선.
폰트 크기 제어 API : SetDirectTextBaseFontSize로 행 높이와 독립적으로 DWrite 기본 em 크기 설정 가능.
주요 개선 사항
위젯 텍스트 렌더링 전면 DWrite 경로로 마이그레이션 (Button, Tab, Menu 등 전체).
텍스트-런 API 도입 : 글리프별 대신 런 단위 COM 호출 → 오버헤드 감소.
캐싱 최적화 : TryMeasureDirectText 결과를 재사용해 이중 래스터라이제이션 제거.
폰트 아틀라스 진단/캐시 옵션 추가 : 환경변수로 디스크 캐시 토글, 빌드 추적, 텍스처 덤프 가능.
Vulkan 폰트 명령 진단 강화 : 명령 추적 및 바운드 검증 환경변수 제공.
캐시 무효화 로직 개선 : UiFontResource에 CodepointSignature 추가, 프레임별 코드포인트 스냅숏 도입.
주요 버그 수정
텍스처 ID 충돌 해결 : 동적 아틀라스와 DWrite 텍스트 ID 범위 분리.
한글 미출력 문제 해결 : 공백 런 처리 개선으로 공백 전용 런 알파 문제 제거.
폰트 크기 불일치 수정 : LineHeight 대신 정확한 em 크기 사용.
수직 중앙 정렬 문제 해결 : Y 오프셋 추가로 행 높이 내 중앙 배치.
Vulkan 스테이징 버퍼 레이스 수정 : 펜스 대기 후 메모리 쓰기 순서 보장.
스왑체인 재생성 오류 처리 개선 : 실패 시 안전하게 false 반환.
버퍼 크기 검증 강화 : 포맷별 정확한 바이트 계산 및 부족 시 패딩 처리.
기존 폰트 아틀라스를 모두 걷어내고 이제 DWrite의 글리프 방식으로 대체하였습니다.
curl -sL https://raw.githubusercontent.com/dimohy/Duxel/refs/heads/main/samples/fba/font_style_validation_fba.cs -o - | dotnet run -
1개의 좋아요
0.2.0-preview
주요 기능 추가
쇼케이스/샘플 범위 확장 — 종합 워크스페이스로 확장 구현
확장 포인트 문서화 및 샘플 보강 — 고급 UI 조합 경로 확인 가능하도록 구현
주요 개선 사항
쇼케이스 배치와 레이아웃 — 배치 안정화 정리
내장 데모 창 동작 — 위치/크기 및 일관성 정리
릴리스 문서 — 링크 및 메타데이터 동기화 정리
주요 버그 수정
멀티라인 입력과 마크다운 줄바꿈 — carriage return 제거 수정
한글 IME 입력 지연 — 즉시 반영 및 끊김 제거 수정
쇼케이스 레이아웃 회귀 — clipping/겹침/폭 혼선 수정
라이브러리 top-most 동작 — 창 스택 순서 안정화 구현
curl -sL https://raw.githubusercontent.com/dimohy/Duxel/refs/heads/main/samples/fba/all_features.cs -o - | dotnet run -
Duxel에서 제공하는 기능들을 전체 점검하였고 이를 확인할 수 있는 all_features.cs를 개선하였습니다.
다음으로 Skil.NET 의존성을 제거하고 DSL 사용 샘플을 다양하게 추가하면서 Duxel의 가능성을 좀 더 점검할 예정입니다.
※ Markdown Preview 기능을 추가했습니다. 커스텀 위젯을 만들 수 있게 하였고 그 첫번째 위젯입니다.
all_features.cs를 통해 확인할 수 있어요! ^^
5개의 좋아요
0.2.2-preview
주요 기능 추가
[기능] DSL 스크린 런타임 (UiDslScreen) — 핫리로드(managed)와 소스 생성(NativeAOT) 통합 DSL 렌더링, .duxel-theme 핫리로드, RequestTheme() API, 이벤트/값 바인딩 통합.
[기능] DSL 이벤트/값 바인딩 — UiDslEventBinder(fluent per-id 버튼/체크박스 콜백)와 UiDslValueBinder(fluent IUiDslValueSource 래퍼)로 인터페이스 수동 구현 제거.
[기능] DSL 제어 흐름 — If/ElseIf/Else, Visible, ForEach(범위 기반 템플릿 확장), Switch/Case/Default, Set, Text Bind="key" 데이터 바인딩.
[기능] 테마 프리셋 10종 — ImGuiDark, ImGuiLight, ImGuiClassic, Nord, SolarizedDark, SolarizedLight, Dracula, Monokai, CatppuccinMocha, GitHubDark 즉시 전환 지원.
[기능] ThemeDemo 샘플 — DSL 기반 테마 핫리로드 데모 앱, 테마 선택 Combo, 제어 흐름 데모, NativeAOT 지원.
[기능] 테마 시스템 전면 개편 — UiTheme struct를 110 색 InlineArray로 재설계, 위젯별 색상 토큰(UiStyleColor enum), InitWidgetDefaults() 기본값 캐스케이드, .duxel-theme 파일 파서의 베이스 프리셋 상속.
주요 개선 사항
[개선] 위젯 테마 세분화 — 기존 ~35개 글로벌 색상에서 110개 위젯별 토큰으로 확장, Button/Checkbox/RadioButton/Input/Slider/Drag/Combo/Selectable/MenuItem/Tab/TreeNode/Table/Tooltip/ListBox/ProgressBar/Separator의 border·state 개별 지정.
[개선] 위젯 border 렌더링 — Button, Checkbox, RadioButton, Input, Slider, Drag, Combo, ListBox, ProgressBar 위젯이 hover/active 상태별 독립 border 색상 렌더링.
[개선] 텍스트/불릿 수직 중앙 정렬 — glyph metrics 기반 시각 중심 계산으로 기존 heuristic ascent offset 대체.
[개선] Combo 위젯 이벤트 알림 — 값 변경 시 EventSink.OnButton(id) 발동, DSL 이벤트 핸들러 반응형 처리 가능.
[개선] DuxelAppOptions.Screen — nullable optional에서 required로 변경, 폐기된 DuxelDslOptions 진입점 및 UiDslBindings 클래스 제거.
[개선] ProgressBar accent 색상 — ProgressBarFill이 SliderGrab 대신 PlotHistogram 사용 (Dear ImGui 기준 일치).
주요 버그 수정
[버그] Text Bind 위치 인자 간섭 — ReadOptionalString("Bind")가 텍스트 내용까지 소비하던 문제, ReadNamedString("Bind", null)로 수정.
[버그] 제어 흐름 스킵 경로 스타일 leak — 스킵된 컨테이너의 EndNode에서 StyleColorPushCounts 미정리 수정.
[버그] Selectable 이벤트 누락 — Selectable 상태 변경 시 OnButton과 OnCheckbox 이벤트 모두 발동하도록 수정.
| Main.ui
Window "Theme Hot-Reload Demo"
MenuBar
Menu "File"
MenuItem "file.new" "New"
MenuItem "file.open" "Open"
MenuItem "file.save" "Save"
Menu "Edit"
MenuItem "edit.undo" "Undo"
MenuItem "edit.redo" "Redo"
Menu "View"
MenuItem "view.theme" "Theme Info"
Text Bind="status" Default="Ready"
Separator
SeparatorText "Theme"
Combo "theme" "Theme" Items="Dark,Light,Classic,Nord,Solarized Dark,Solarized Light,Dracula,Monokai,Catppuccin Mocha,GitHub Dark"
Separator
SeparatorText "Buttons"
Row
Button "primary" "Primary Action"
Button "secondary" "Small" Size=80,0
ArrowButton "arrow_r" Right
SeparatorText "Toggles"
Checkbox "neon" "Enable Neon Glow" true
Checkbox "wireframe" "Wireframe Mode" false
Row
RadioButton "opt" "Option A" 0
RadioButton "opt" "Option B" 1
RadioButton "opt" "Option C" 2
SeparatorText "Input"
InputText "name" "Name" "Hello Cyberpunk"
SeparatorText "Sliders & Drags"
SliderFloat "intensity" "Intensity" 0.0 1.0
DragFloat "value" "Value" 42.0 0.5 0.0 100.0
ProgressBar 0.72 Size=0,0 Overlay="Loading..."
SeparatorText "Selection"
Combo "style" "Style" "Neon"
Selectable "style.neon" "Neon"
Selectable "style.vapor" "Vapor"
Selectable "style.retro" "Retro"
Selectable "style.synth" "Synth"
ListBox "channels" "Channels" "Alpha"
Selectable "ch.alpha" "Alpha"
Selectable "ch.bravo" "Bravo"
Selectable "ch.charlie" "Charlie"
Selectable "ch.delta" "Delta"
SeparatorText "Tree & Collapsing"
CollapsingHeader "System Info"
TreeNode "GPU"
Text "Vendor: Cyberdyne"
Text "VRAM: 16 GB"
TreeNode "Display"
Text "Resolution: 3840x2160"
Text "Refresh: 144 Hz"
SeparatorText "Tabs"
TabBar "DemoTabs"
TabItem "Overview"
Text "Theme overview panel."
TabItem "Settings"
Text "Adjust theme parameters here."
SeparatorText "Selectable"
Selectable "profile.1" "Profile 1"
Selectable "profile.2" "Profile 2"
Selectable "profile.3" "Profile 3"
SeparatorText "Tooltip Demo"
Button "hover_me" "Hover Me"
Text "Edit cyberpunk.duxel-theme to see hot-reload!"
# ── Control Flow Demo (self-contained) ─────────────────
SeparatorText "Control Flow: If / Else"
Checkbox "cf_alpha" "Alpha" true
Checkbox "cf_beta" "Beta" false
If Bind="cf_alpha"
Text "Alpha is ON"
ElseIf Bind="cf_beta"
Text "Beta is ON"
Else
Text "Both OFF"
SeparatorText "Control Flow: Visible"
Checkbox "cf_show" "Show hidden section" false
Visible Bind="cf_show"
Text "This text is only visible when the checkbox above is checked"
SeparatorText "Control Flow: ForEach"
ForEach Range=1,5
Button "item_{_index}" "Item {_index}"
SeparatorText "Control Flow: Switch / Case"
Selectable "cf_mode.A" "Mode A"
Selectable "cf_mode.B" "Mode B"
Selectable "cf_mode.C" "Mode C"
Switch Bind="cf_mode"
Case "A"
Text "Mode A selected"
Case "B"
Text "Mode B selected"
Case "C"
Text "Mode C selected"
Default
Text "No mode selected — click one above"
| cyberpunk.duxel-theme
Theme "Cyberpunk" : Dark
// ── Global palette ──
WindowBg = #0D0D1A
Text = #E0E0F0
TextDisabled = #55557A
Border = #2A1A4A
// ── Window ──
TitleBg = #0A0A18
TitleBgActive = #1A0A2A
WindowTitleText = #00FFCC
// ── Buttons ──
Button = #1A1A3A
ButtonHovered = #2A2A5A
ButtonActive = #00CCAA
ButtonBorder = #00FFCC
ButtonText = #00FFCC
// ── Checkbox ──
FrameBg = #1A1A3A
FrameBgHovered = #2A2A5A
FrameBgActive = #00CCAA
CheckMark = #00FFCC
CheckboxBg = #1A1A3A
CheckboxBgHovered = #2A2A5A
CheckboxBgActive = #00CCAA
CheckboxText = #E0E0F0
// ── Radio Button ──
RadioButtonBg = #1A1A3A
RadioButtonBgHovered = #2A2A5A
RadioButtonBgActive = #00CCAA
RadioButtonText = #E0E0F0
// ── Progress Bar ──
ProgressBarBg = #1A1A3A
ProgressBarFill = #00FFCC
// ── Input ──
InputBg = #12122A
InputBgHovered = #1A1A3A
InputText = #E0E0F0
InputSelectionBg = #4400CCAA
// ── Slider ──
SliderBg = #1A1A3A
SliderBgHovered = #2A2A5A
SliderGrab = #00FFCC
SliderGrabActive = #00EEBB
SliderText = #E0E0F0
// ── Drag ──
DragBg = #1A1A3A
DragBgHovered = #2A2A5A
DragBgActive = #00CCAA
DragText = #E0E0F0
// ── Combo ──
ComboBg = #1A1A3A
ComboBgHovered = #2A2A5A
ComboBgActive = #00CCAA
ComboPopupBg = #12122A
ComboText = #E0E0F0
// ── ListBox ──
ListBoxBg = #12122A
ListBoxItemBgActive = #00CCAA
ListBoxItemBgHovered = #2A2A5A
ListBoxItemText = #E0E0F0
// ── Selectable ──
SelectableBgActive = #00CCAA
SelectableBgHovered = #2A2A5A
SelectableText = #E0E0F0
// ── Menu ──
MenuBarBg = #0A0A18
PopupBg = #12122A
MenuItemBgActive = #00CCAA
MenuItemBgHovered = #2A2A5A
MenuItemText = #E0E0F0
MenuItemTextDisabled = #55557A
// ── Tree ──
TreeNodeBg = #1A1A3A
TreeNodeBgActive = #00CCAA
TreeNodeBgHovered = #2A2A5A
TreeNodeText = #E0E0F0
// ── Tab ──
Tab = #1A1A3A
TabHovered = #2A2A5A
TabActive = #2A1A4A
TabText = #E0E0F0
// ── Table ──
TableHeaderBg = #1A1A3A
TableBorder = #2A1A4A
TableRowBg0 = #0D0D1A
TableRowBg1 = #12122A
TableHeaderText = #00FFCC
// ── Tooltip ──
TooltipBg = #12122A
TooltipText = #E0E0F0
// ── Separator ──
Separator = #2A1A4A
SeparatorLabelText = #00FFCC
// ── Scrollbar ──
ScrollbarBg = #0A0A18
ScrollbarGrab = #2A2A5A
ScrollbarGrabHovered = #3A3A6A
ScrollbarGrabActive = #00CCAA
3개의 좋아요