Last active
January 18, 2026 10:11
-
-
Save Hammer2900/186c2cddd1226347e3559fb312ca8f3d to your computer and use it in GitHub Desktop.
torent client gui on odin, place to example folder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import syl "../.." | |
| import renderer "../../renderer/raylib" | |
| import rl "vendor:raylib" | |
| import "core:fmt" | |
| // --- Colors (uTorrent Palette) --- | |
| UT_BG :: [4]u8{ 240, 240, 240, 255 } | |
| UT_WHITE :: [4]u8{ 255, 255, 255, 255 } | |
| UT_BORDER :: [4]u8{ 180, 180, 180, 255 } | |
| UT_HEADER :: [4]u8{ 235, 235, 235, 255 } | |
| UT_HEADER_TEXT :: [4]u8{ 80, 80, 80, 255 } | |
| UT_TEXT :: [4]u8{ 0, 0, 0, 255 } | |
| UT_BLUE_SELECT :: [4]u8{ 51, 153, 255, 255 } | |
| UT_BLUE_BG :: [4]u8{ 220, 240, 255, 255 } | |
| UT_GREEN :: [4]u8{ 50, 205, 50, 255 } | |
| UT_GREEN_DARK :: [4]u8{ 34, 139, 34, 255 } | |
| UT_SIDEBAR_GRAY :: [4]u8{ 100, 100, 100, 255 } | |
| UT_DARK :: [4]u8{ 30, 30, 30, 255 } | |
| BLANK :: [4]u8{ 0, 0, 0, 0 } | |
| GREEN :: [4]u8{ 50, 205, 50, 255 } | |
| RED :: [4]u8{ 255, 100, 100, 255 } | |
| ORANGE :: [4]u8{ 255, 165, 0, 255 } | |
| SCREEN_W :: 1280 | |
| SCREEN_H :: 800 | |
| // --- Column Widths --- | |
| COL_NAME_W :: 350.0 | |
| COL_SIZE_W :: 100.0 | |
| COL_DONE_W :: 140.0 | |
| COL_STATUS_W :: 125.0 | |
| COL_DOWN_W :: 110.0 | |
| COL_UP_W :: 110.0 | |
| COL_ETA_W :: 120.0 | |
| active_tab_label := "General" | |
| // --- Data Structures --- | |
| Torrent :: struct { | |
| id: string, | |
| name: string, | |
| size: string, | |
| percent: f32, | |
| status: string, | |
| down_speed: string, | |
| up_speed: string, | |
| seeds: string, | |
| peers: string, | |
| } | |
| // Dummy Data | |
| torrents := [?]Torrent{ | |
| { "t1", "Linux_Distro_2024_x64.iso", "4.2 GB", 1.0, "Seeding", "0 kB/s", "520 kB/s", "0 (45)", "12 (200)" }, | |
| { "t2", "Odin_Lang_Documentation.pdf", "15 MB", 1.0, "Seeding", "0 kB/s", "0.1 kB/s", "0 (10)", "2 (5)" }, | |
| { "t3", "Big_Buck_Bunny_4K.mkv", "12.5 GB", 0.45, "Downloading", "12.5 MB/s", "1.2 MB/s", "45 (120)", "80 (500)" }, | |
| { "t4", "Indie_Game_Assets_Pack_v2", "850 MB", 0.12, "Downloading", "2.1 MB/s", "50 kB/s", "12 (30)", "25 (60)" }, | |
| { "t5", "Old_Family_Photos_Archive.zip", "3.1 GB", 0.0, "Queued", "0 kB/s", "0 kB/s", "0 (0)", "0 (0)" }, | |
| { "t6", "Raylib_Source_Code.tar.gz", "50 MB", 0.88, "Downloading", "500 kB/s", "10 kB/s", "5 (15)", "3 (10)" }, | |
| } | |
| // --- State --- | |
| Camera_State :: struct { | |
| camera: rl.Camera2D, | |
| dragging: bool, | |
| last_mouse_pos: rl.Vector2, | |
| } | |
| camera: Camera_State | |
| selected_torrent_id := "t3" | |
| // Animated state | |
| Hover_State :: struct { | |
| is_hovered: bool, | |
| } | |
| hover_states: map[string]Hover_State | |
| // --- Styles --- | |
| style_sheet := syl.StyleSheet { | |
| box = { | |
| default = { | |
| border_color = BLANK, | |
| transitions = { | |
| background_color = { duration = 0.15, ease = .Sine_Out }, | |
| }, | |
| }, | |
| }, | |
| text = { | |
| color = UT_TEXT, | |
| font_size = 20, | |
| } | |
| } | |
| // --- Helpers --- | |
| init_hover :: proc(id: string) { | |
| if id not_in hover_states { | |
| hover_states[id] = Hover_State{ is_hovered = false } | |
| } | |
| } | |
| update_hover :: proc(box: ^syl.Box, hover_color, normal_color: [4]u8) { | |
| if box.id == "" do return | |
| state, ok := &hover_states[box.id] | |
| if !ok do return | |
| mouse_pos := rl.GetMousePosition() | |
| mouse_pos.x -= camera.camera.target.x | |
| mouse_pos.y -= camera.camera.target.y | |
| box_rect := rl.Rectangle{ | |
| box.global_position.x, | |
| box.global_position.y, | |
| box.size.x, | |
| box.size.y | |
| } | |
| collide := rl.CheckCollisionPointRec(mouse_pos, box_rect) | |
| if collide && !state.is_hovered { | |
| state.is_hovered = true | |
| syl.animate_color(&box.style.background_color, hover_color, 0.15) | |
| } else if !collide && state.is_hovered { | |
| state.is_hovered = false | |
| syl.animate_color(&box.style.background_color, normal_color, 0.15) | |
| } | |
| } | |
| update_all_hovers :: proc(element: ^syl.Element) { | |
| #partial switch element.type { | |
| case .Box: | |
| box := cast(^syl.Box)element | |
| // Check specific IDs for different hover effects | |
| switch { | |
| case len(box.id) > 0 && box.id[0:3] == "btn": | |
| update_hover(box, [4]u8{ 220, 220, 220, 255 }, UT_HEADER) | |
| case len(box.id) > 0 && box.id[0:4] == "side": | |
| update_hover(box, UT_BLUE_BG, BLANK) | |
| case len(box.id) > 0 && box.id[0:3] == "row": | |
| bg := box.id == fmt.tprintf("row_%s", selected_torrent_id) ? UT_BLUE_BG : UT_WHITE | |
| update_hover(box, [4]u8{ 245, 250, 255, 255 }, bg) | |
| case len(box.id) > 0 && box.id[0:3] == "tab": | |
| // 1. Эффект наведения (как было) | |
| update_hover(box, [4]u8{ 250, 250, 250, 255 }, UT_HEADER) | |
| // 2. ЛОГИКА КЛИКА (ДОБАВЛЕНО) | |
| mouse_pos := rl.GetMousePosition() | |
| mouse_pos.x -= camera.camera.target.x | |
| mouse_pos.y -= camera.camera.target.y | |
| box_rect := rl.Rectangle{ | |
| box.global_position.x, | |
| box.global_position.y, | |
| box.size.x, | |
| box.size.y | |
| } | |
| // Если курсор внутри и нажата левая кнопка | |
| if rl.CheckCollisionPointRec(mouse_pos, box_rect) && rl.IsMouseButtonPressed(.LEFT) { | |
| // box.id имеет вид "tab_Name", отрезаем первые 4 символа | |
| active_tab_label = box.id[4:] | |
| } | |
| } | |
| for child in box.children { | |
| update_all_hovers(child) | |
| } | |
| } | |
| } | |
| // --- Components --- | |
| toolbar_btn :: proc(icon: string, label: string, color: [4]u8 = UT_TEXT) -> ^syl.Element { | |
| id := fmt.tprintf("btn_%s", label) | |
| init_hover(id) | |
| return syl.box( | |
| id = id, | |
| padding_left = 10, | |
| padding_right = 10, | |
| padding_top = 6, | |
| padding_bottom = 6, | |
| border_radius = 3, | |
| gap = 6, | |
| layout_direction = .Left_To_Right, | |
| background_color = UT_HEADER, | |
| children = { | |
| syl.box( | |
| width = 14, | |
| height = 14, | |
| border_radius = 7, | |
| border_color = color, | |
| background_color = BLANK | |
| ), | |
| syl.text(label, color = color, wrap = false), | |
| } | |
| ) | |
| } | |
| sidebar_item :: proc(label: string, count: string = "", is_selected: bool = false) -> ^syl.Element { | |
| id := fmt.tprintf("side_%s", label) | |
| init_hover(id) | |
| bg := is_selected ? UT_BLUE_BG : BLANK | |
| txt_col := is_selected ? UT_TEXT : UT_SIDEBAR_GRAY | |
| return syl.box( | |
| id = id, | |
| width_sizing = .Expand, | |
| padding_left = 8, | |
| padding_right = 8, | |
| // БЫЛО 6, СТАЛО 4 (список станет компактнее и ближе к заголовку) | |
| padding_top = 4, | |
| padding_bottom = 4, | |
| layout_direction = .Left_To_Right, | |
| background_color = bg, | |
| border_radius = 2, | |
| children = { | |
| syl.text(label, color = txt_col, wrap = false, font_size = 20), | |
| syl.box(sizing = syl.Expand, border_color = BLANK), | |
| count != "" ? syl.text(count, color = txt_col) : nil, | |
| } | |
| ) | |
| } | |
| header_cell :: proc(label: string, w: f32) -> ^syl.Element { | |
| return syl.box( | |
| width = w, | |
| height = 26, | |
| padding_left = 8, | |
| padding_top = 6, | |
| padding_bottom = 6, | |
| background_color = UT_HEADER, | |
| border_color = UT_BORDER, | |
| children = { | |
| syl.text(label, color = UT_HEADER_TEXT, wrap = false, font_size = 20), | |
| } | |
| ) | |
| } | |
| torrent_row :: proc(t: Torrent, is_selected: bool) -> ^syl.Element { | |
| id := fmt.tprintf("row_%s", t.id) | |
| init_hover(id) | |
| bg := is_selected ? UT_BLUE_BG : UT_WHITE | |
| bar_width := max(0, (COL_DONE_W - 60) * t.percent) | |
| return syl.box( | |
| id = id, | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| height = 38, | |
| background_color = bg, | |
| children = { | |
| // Name | |
| syl.box( | |
| width = COL_NAME_W, | |
| padding_left = 6, | |
| padding_top = 10, | |
| padding_bottom = 6, | |
| border_color = BLANK, | |
| children = { | |
| syl.text(t.name, wrap = false, font_size = 20) | |
| } | |
| ), | |
| // Size | |
| syl.box( | |
| width = COL_SIZE_W, | |
| padding_left = 6, | |
| padding_top = 10, | |
| padding_bottom = 6, | |
| border_color = BLANK, | |
| children = { | |
| syl.text(t.size, wrap = false, font_size = 20) | |
| } | |
| ), | |
| // Progress | |
| syl.box( | |
| width = COL_DONE_W, | |
| padding_left = 8, | |
| padding_right = 8, | |
| padding_top = 5, | |
| padding_bottom = 5, | |
| border_color = BLANK, | |
| layout_direction = .Left_To_Right, | |
| gap = 8, | |
| children = { | |
| syl.box( | |
| width = COL_DONE_W - 60, | |
| height = 16, | |
| background_color = [4]u8{ 230, 230, 230, 255 }, | |
| border_color = UT_BORDER, | |
| border_radius = 2, | |
| children = { | |
| syl.box( | |
| width = bar_width, | |
| height = 14, | |
| background_color = (t.percent == 1.0 ? UT_GREEN_DARK : UT_GREEN), | |
| border_radius = 1, | |
| ) | |
| } | |
| ), | |
| syl.text(fmt.tprintf("%.0f%%", t.percent * 100), wrap = false, font_size = 20), | |
| } | |
| ), | |
| // Status | |
| syl.box( | |
| width = COL_STATUS_W, | |
| padding_left = 8, | |
| padding_top = 6, | |
| padding_bottom = 6, | |
| border_color = BLANK, | |
| children = { | |
| syl.text(t.status, wrap = false, font_size = 20) | |
| } | |
| ), | |
| // Down Speed | |
| syl.box( | |
| width = COL_DOWN_W, | |
| padding_left = 8, | |
| padding_top = 6, | |
| padding_bottom = 6, | |
| border_color = BLANK, | |
| children = { | |
| syl.text(t.down_speed, color = UT_GREEN_DARK, wrap = false, font_size = 20) | |
| } | |
| ), | |
| // Up Speed | |
| syl.box( | |
| width = COL_UP_W, | |
| padding_left = 8, | |
| padding_top = 6, | |
| padding_bottom = 6, | |
| border_color = BLANK, | |
| children = { | |
| syl.text(t.up_speed, color = UT_BLUE_SELECT, wrap = false, font_size = 20) | |
| } | |
| ), | |
| // Seeds | |
| syl.box( | |
| width = COL_ETA_W, | |
| padding_left = 8, | |
| padding_top = 6, | |
| padding_bottom = 6, | |
| border_color = BLANK, | |
| children = { | |
| syl.text(t.seeds, wrap = false, font_size = 20) | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| tab_button :: proc(label: string, is_active: bool = false) -> ^syl.Element { | |
| id := fmt.tprintf("tab_%s", label) | |
| init_hover(id) | |
| bg := is_active ? UT_WHITE : UT_HEADER | |
| return syl.box( | |
| id = id, | |
| padding_left = 12, | |
| padding_right = 12, | |
| padding_top = 6, | |
| padding_bottom = 6, | |
| background_color = bg, | |
| border_radius = 2, | |
| children = { | |
| syl.text(label, wrap = false) | |
| } | |
| ) | |
| } | |
| get_tab_content :: proc() -> ^syl.Element { | |
| if active_tab_label == "General" { | |
| return syl.box( | |
| sizing = syl.Expand, | |
| padding = 12, | |
| gap = 8, | |
| children = { | |
| syl.text("Transfer Information", color = UT_BLUE_SELECT, wrap = false, font_size = 20), | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| gap = 30, | |
| width_sizing = .Expand, | |
| children = { | |
| syl.box(gap = 4, children = { | |
| syl.text("Time Elapsed: 2h 15m", wrap = false, font_size = 20), | |
| syl.text("Downloaded: 4.5 GB", wrap = false, font_size = 20), | |
| syl.text("Download Speed: 12.5 MB/s", wrap = false, font_size = 20), | |
| }), | |
| syl.box(gap = 4, children = { | |
| syl.text("Remaining: 8.0 GB", wrap = false, font_size = 20), | |
| syl.text("Uploaded: 1.2 GB", wrap = false, font_size = 20), | |
| syl.text("Upload Speed: 1.2 MB/s", wrap = false, font_size = 20), | |
| }), | |
| syl.box(gap = 4, children = { | |
| syl.text("Seeds: 45 of 120", wrap = false, font_size = 20), | |
| syl.text("Peers: 80 of 500", wrap = false, font_size = 20), | |
| syl.text("Share Ratio: 0.266", wrap = false, font_size = 20), | |
| }), | |
| } | |
| ), | |
| syl.box(height = 8, border_color = BLANK), | |
| syl.box( | |
| width_sizing = .Expand, | |
| height = 70, | |
| background_color = UT_DARK, | |
| border_color = UT_BORDER, | |
| border_radius = 2, | |
| padding = 8, | |
| children = { | |
| syl.text("[ Speed Graph - Real-time Visualization ]", color = GREEN, wrap = false, font_size = 20), | |
| } | |
| ), | |
| } | |
| ) | |
| } else { | |
| return syl.box( | |
| sizing = syl.Expand, | |
| padding = 20, | |
| children = { | |
| syl.text( | |
| fmt.tprintf("Info for tab '%s' is not implemented yet.", active_tab_label), | |
| color = UT_SIDEBAR_GRAY, | |
| font_size = 20, | |
| wrap = false | |
| ), | |
| syl.box(height = 10, border_color = BLANK), | |
| syl.text("(Waiting for data...)", color = UT_SIDEBAR_GRAY, font_size = 16), | |
| } | |
| ) | |
| } | |
| } | |
| // --- Main Layout --- | |
| make_utorrent_app :: proc() -> ^syl.Element { | |
| return syl.box( | |
| size = { SCREEN_W, SCREEN_H }, | |
| background_color = UT_BG, | |
| style_sheet = &style_sheet, | |
| children = { | |
| // 1. TOOLBAR | |
| syl.box( | |
| width_sizing = .Expand, | |
| height = 48, | |
| padding = 8, | |
| gap = 8, | |
| layout_direction = .Left_To_Right, | |
| background_color = UT_HEADER, | |
| border_color = UT_BORDER, | |
| children = { | |
| toolbar_btn("+", "Add", GREEN), | |
| toolbar_btn("-", "Remove", RED), | |
| syl.box(width = 1, height = 30, background_color = UT_BORDER), | |
| toolbar_btn(">", "Start", GREEN), | |
| toolbar_btn("||", "Pause", ORANGE), | |
| toolbar_btn("x", "Stop", RED), | |
| syl.box(sizing = syl.Expand, border_color = BLANK), | |
| toolbar_btn("?", "Search", UT_TEXT), | |
| } | |
| ), | |
| // 2. MAIN CONTENT | |
| syl.box( | |
| sizing = syl.Expand, | |
| layout_direction = .Left_To_Right, | |
| gap = 2, | |
| children = { | |
| // SIDEBAR | |
| syl.box( | |
| width = 200, | |
| height_sizing = .Expand, | |
| background_color = UT_WHITE, | |
| padding = 8, | |
| gap = 0, | |
| border_color = UT_BORDER, | |
| children = { | |
| syl.box( | |
| padding_bottom = 2, | |
| children = { syl.text("TORRENTS", color = UT_HEADER_TEXT, wrap = false, font_size = 20) } | |
| ), | |
| sidebar_item("Downloading", "3", false), | |
| sidebar_item("Seeding", "2", false), | |
| sidebar_item("Completed", "2", false), | |
| sidebar_item("Active", "5", true), | |
| sidebar_item("Inactive", "1", false), | |
| syl.box(height = 16, border_color = BLANK), | |
| syl.box( | |
| padding_bottom = 2, | |
| children = { syl.text("LABELS", color = UT_HEADER_TEXT, wrap = false, font_size = 20) } | |
| ), | |
| sidebar_item("No Label", "6", false), | |
| syl.box(height = 16, border_color = BLANK), | |
| syl.box( | |
| padding_bottom = 2, | |
| children = { syl.text("FEEDS", color = UT_HEADER_TEXT, wrap = false, font_size = 20) } | |
| ), | |
| sidebar_item("All Feeds", "0", false), | |
| } | |
| ), | |
| // GRID AREA | |
| syl.box( | |
| sizing = syl.Expand, | |
| background_color = UT_WHITE, | |
| border_color = UT_BORDER, | |
| children = { | |
| // Headers | |
| syl.box( | |
| width_sizing = .Expand, | |
| layout_direction = .Left_To_Right, | |
| children = { | |
| header_cell("Name", COL_NAME_W), | |
| header_cell("Size", COL_SIZE_W), | |
| header_cell("Done", COL_DONE_W), | |
| header_cell("Status", COL_STATUS_W), | |
| header_cell("Down Speed", COL_DOWN_W), | |
| header_cell("Up Speed", COL_UP_W), | |
| header_cell("Seeds (Peers)", COL_ETA_W), | |
| } | |
| ), | |
| // Rows | |
| syl.box( | |
| width_sizing = .Expand, | |
| height_sizing = .Expand, | |
| children = { | |
| torrent_row(torrents[0], torrents[0].id == selected_torrent_id), | |
| torrent_row(torrents[1], torrents[1].id == selected_torrent_id), | |
| torrent_row(torrents[2], torrents[2].id == selected_torrent_id), | |
| torrent_row(torrents[3], torrents[3].id == selected_torrent_id), | |
| torrent_row(torrents[4], torrents[4].id == selected_torrent_id), | |
| torrent_row(torrents[5], torrents[5].id == selected_torrent_id), | |
| } | |
| ), | |
| } | |
| ), | |
| } | |
| ), | |
| // 3. DETAILS PANEL | |
| syl.box( | |
| width_sizing = .Expand, | |
| height = 220, | |
| background_color = UT_WHITE, | |
| border_color = UT_BORDER, | |
| children = { | |
| // Tabs | |
| syl.box( | |
| width_sizing = .Expand, | |
| height = 32, | |
| background_color = UT_HEADER, | |
| layout_direction = .Left_To_Right, | |
| padding = 4, | |
| gap = 3, | |
| children = { | |
| // ИСПРАВЛЕНИЕ 1: Сравниваем с переменной active_tab_label | |
| tab_button("General", active_tab_label == "General"), | |
| tab_button("Trackers", active_tab_label == "Trackers"), | |
| tab_button("Peers", active_tab_label == "Peers"), | |
| tab_button("Pieces", active_tab_label == "Pieces"), | |
| tab_button("Files", active_tab_label == "Files"), | |
| tab_button("Speed", active_tab_label == "Speed"), | |
| tab_button("Logger", active_tab_label == "Logger"), | |
| } | |
| ), | |
| // ИСПРАВЛЕНИЕ 2: Только вызов функции, старый код удален | |
| get_tab_content(), | |
| } | |
| ), | |
| // 4. STATUS BAR | |
| syl.box( | |
| width_sizing = .Expand, | |
| height = 26, | |
| background_color = UT_HEADER, | |
| padding_left = 10, | |
| padding_right = 10, | |
| padding_top = 5, | |
| padding_bottom = 5, | |
| layout_direction = .Left_To_Right, | |
| border_color = UT_BORDER, | |
| children = { | |
| syl.text("DHT: 320 nodes", wrap = false), | |
| syl.box(sizing = syl.Expand, border_color = BLANK), | |
| syl.text("D: 15.2 MB/s", color = UT_GREEN_DARK, wrap = false), | |
| syl.box(width = 20, border_color = BLANK), | |
| syl.text("U: 1.8 MB/s", color = UT_BLUE_SELECT, wrap = false), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| main :: proc() { | |
| rl.InitWindow(SCREEN_W, SCREEN_H, "uTorrent Clone - Syl UI Demo") | |
| rl.SetTargetFPS(60) | |
| camera.camera = rl.Camera2D{ zoom = 1.0 } | |
| hover_states = make(map[string]Hover_State) | |
| for !rl.WindowShouldClose() { | |
| // Animate progress | |
| torrents[2].percent += 0.0003 | |
| if torrents[2].percent > 1.0 do torrents[2].percent = 0.45 | |
| torrents[3].percent += 0.0002 | |
| if torrents[3].percent > 1.0 do torrents[3].percent = 0.12 | |
| torrents[5].percent += 0.0008 | |
| if torrents[5].percent > 1.0 do torrents[5].percent = 0.88 | |
| app := make_utorrent_app() | |
| syl.calculate_layout(app) | |
| update_all_hovers(app) | |
| syl.update_transitions() | |
| rl.BeginDrawing() | |
| rl.ClearBackground(cast(rl.Color)UT_BG) | |
| rl.BeginMode2D(camera.camera) | |
| renderer.draw(app) | |
| rl.EndMode2D() | |
| rl.EndDrawing() | |
| } | |
| delete(hover_states) | |
| rl.CloseWindow() | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import syl "../.." | |
| import renderer "../../renderer/raylib" | |
| import "core:math/ease" | |
| import rl "vendor:raylib" | |
| // --- Color palette --- | |
| GREEN :: [4]u8{ 195, 214, 44, 255 } | |
| DARK_GREEN :: [4]u8{ 110, 210, 40, 255 } | |
| BLACK :: [4]u8{ 25, 25, 25, 255 } | |
| BLANK :: [4]u8{ 0, 0, 0, 0 } | |
| RED :: [4]u8{ 255, 100, 100, 255 } | |
| BLUE :: [4]u8{ 100, 150, 255, 255 } | |
| PURPLE :: [4]u8{ 180, 100, 255, 255 } | |
| ORANGE :: [4]u8{ 255, 165, 0, 255 } | |
| GRAY :: [4]u8{ 100, 100, 100, 255 } | |
| LIGHT_GRAY :: [4]u8{ 150, 150, 150, 255 } | |
| CYAN :: [4]u8{ 100, 255, 255, 255 } | |
| DARK_BG :: [4]u8{ 20, 20, 20, 255 } | |
| SCREEN_W :: 1200 | |
| SCREEN_H :: 800 | |
| // --- Global State --- | |
| // Camera state | |
| Camera_State :: struct { | |
| camera: rl.Camera2D, | |
| dragging: bool, | |
| last_mouse_pos: rl.Vector2, | |
| } | |
| camera: Camera_State | |
| // Animation state for the progress bar demo | |
| progress_value : f32 = 0.0 | |
| progress_dir : f32 = 1.0 | |
| // Animated hover state tracking | |
| Animated_Box_State :: struct { | |
| is_hovered: bool, | |
| original_color: [4]u8, | |
| hover_color: [4]u8, | |
| } | |
| animated_boxes: map[string]Animated_Box_State | |
| // --- Styles --- | |
| style_sheet := syl.StyleSheet { | |
| box = { | |
| default = { | |
| border_color = GREEN, | |
| transitions = { | |
| background_color = { duration = 0.3, ease = .Cubic_In_Out }, | |
| }, | |
| }, | |
| // Убрали progress_track, так как структура StyleSheet фиксирована | |
| }, | |
| text = { | |
| color = GREEN, | |
| } | |
| } | |
| // --- Demos --- | |
| // Demo 1: Layout Direction Examples | |
| make_layout_demo :: proc() -> ^syl.Element { | |
| return syl.box( | |
| width_sizing = .Expand, | |
| padding = 15, | |
| gap = 10, | |
| background_color = [4]u8{ 30, 30, 30, 255 }, | |
| border_radius = 4, | |
| children = { | |
| syl.text("LAYOUT DIRECTIONS", wrap = false), | |
| // Horizontal layout | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| gap = 8, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.box(id="item1", padding = 10, background_color = RED, children = { syl.text("Item 1") }), | |
| syl.box(id="item2", padding = 10, background_color = BLUE, children = { syl.text("Item 2") }), | |
| syl.box(id="item3", padding = 10, background_color = PURPLE, children = { syl.text("Item 3") }), | |
| } | |
| ), | |
| // Vertical layout | |
| syl.box( | |
| width_sizing = .Expand, | |
| gap = 8, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.box(id="vert1", padding = 10, background_color = GREEN, children = { syl.text("Vertical 1") }), | |
| syl.box(id="vert2", padding = 10, background_color = ORANGE, children = { syl.text("Vertical 2") }), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| // Demo 2: Sizing Modes (Fit, Expand, Fixed) | |
| make_sizing_demo :: proc() -> ^syl.Element { | |
| return syl.box( | |
| width_sizing = .Expand, | |
| padding = 15, | |
| gap = 10, | |
| background_color = [4]u8{ 30, 30, 30, 255 }, | |
| border_radius = 4, | |
| children = { | |
| syl.text("SIZING MODES", wrap = false), | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| gap = 8, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.box(id="fit", padding = 10, background_color = RED, children = { syl.text("Fit") }), | |
| syl.box(id="expand", sizing = syl.Expand, padding = 10, background_color = BLUE, children = { syl.text("Expand") }), | |
| syl.box(id="fixed", width = 100, padding = 10, background_color = PURPLE, children = { syl.text("Fixed 100px") }), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| // Demo 3: Nested layouts | |
| make_nested_demo :: proc() -> ^syl.Element { | |
| return syl.box( | |
| width_sizing = .Expand, | |
| padding = 15, | |
| gap = 10, | |
| background_color = [4]u8{ 30, 30, 30, 255 }, | |
| border_radius = 4, | |
| children = { | |
| syl.text("NESTED LAYOUTS", wrap = false), | |
| syl.box( | |
| width_sizing = .Expand, | |
| gap = 8, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| gap = 8, | |
| children = { | |
| syl.box( | |
| id="leftcol", | |
| sizing = syl.Expand, | |
| gap = 5, | |
| padding = 10, | |
| background_color = RED, | |
| children = { | |
| syl.text("Left Column"), | |
| syl.box(id="nest1", padding = 5, background_color = [4]u8{ 200, 50, 50, 255 }, children = { syl.text("Nested 1") }), | |
| syl.box(id="nest2", padding = 5, background_color = [4]u8{ 200, 50, 50, 255 }, children = { syl.text("Nested 2") }), | |
| } | |
| ), | |
| syl.box( | |
| id="rightcol", | |
| sizing = syl.Expand, | |
| gap = 5, | |
| padding = 10, | |
| background_color = BLUE, | |
| children = { | |
| syl.text("Right Column"), | |
| syl.box(id="nestA", padding = 5, background_color = [4]u8{ 50, 100, 200, 255 }, children = { syl.text("Nested A") }), | |
| syl.box(id="nestB", padding = 5, background_color = [4]u8{ 50, 100, 200, 255 }, children = { syl.text("Nested B") }), | |
| } | |
| ), | |
| } | |
| ), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| // Demo 4: Padding and Gap | |
| make_spacing_demo :: proc() -> ^syl.Element { | |
| return syl.box( | |
| width_sizing = .Expand, | |
| padding = 15, | |
| gap = 10, | |
| background_color = [4]u8{ 30, 30, 30, 255 }, | |
| border_radius = 4, | |
| children = { | |
| syl.text("PADDING & GAP", wrap = false), | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| gap = 15, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.box(id="pad5", padding = 5, background_color = GREEN, children = { syl.text("5px pad") }), | |
| syl.box(id="pad15", padding = 15, background_color = ORANGE, children = { syl.text("15px pad") }), | |
| syl.box(id="pad25", padding = 25, background_color = PURPLE, children = { syl.text("25px pad") }), | |
| } | |
| ), | |
| syl.text("Gap between items: 15px", wrap = false), | |
| } | |
| ) | |
| } | |
| // Demo 5: Interactive hover example | |
| interactive_card :: proc(title, status: string, bg_color: [4]u8, id: string) -> ^syl.Element { | |
| return syl.box( | |
| width_sizing = .Expand, | |
| children = { | |
| syl.box( | |
| id = id, | |
| width_sizing = .Expand, | |
| padding = 12, | |
| background_color = bg_color, | |
| border_color = BLACK, | |
| gap = 5, | |
| children = { | |
| syl.text(title, color = BLACK, wrap = false), | |
| syl.box( | |
| padding = 6, | |
| background_color = [4]u8{ 0, 0, 0, 100 }, | |
| children = { | |
| syl.text(status, color = GREEN), | |
| } | |
| ), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| make_interactive_demo :: proc() -> ^syl.Element { | |
| return syl.box( | |
| width_sizing = .Expand, | |
| padding = 15, | |
| gap = 10, | |
| background_color = [4]u8{ 30, 30, 30, 255 }, | |
| border_radius = 4, | |
| children = { | |
| syl.text("INTERACTIVE ELEMENTS (Hover to animate)", wrap = false), | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| gap = 8, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| interactive_card("System A", "ONLINE", GREEN, "card1"), | |
| interactive_card("System B", "STANDBY", ORANGE, "card2"), | |
| interactive_card("System C", "OFFLINE", RED, "card3"), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| // Demo 6: Complex dashboard | |
| make_dashboard_demo :: proc() -> ^syl.Element { | |
| return syl.box( | |
| sizing = syl.Expand, | |
| padding = 15, | |
| gap = 10, | |
| background_color = [4]u8{ 30, 30, 30, 255 }, | |
| border_radius = 4, | |
| children = { | |
| syl.text("DASHBOARD EXAMPLE", wrap = false), | |
| // Top bar | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| gap = 8, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.box(id="status", padding = 8, background_color = GREEN, children = { syl.text("Status: OK", color = BLACK) }), | |
| syl.box(sizing = syl.Expand, padding = 8, background_color = GRAY, children = { syl.text("Time: 14:32:05") }), | |
| syl.box(id="users", padding = 8, background_color = BLUE, children = { syl.text("Users: 42", color = BLACK) }), | |
| } | |
| ), | |
| // Main content area | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| sizing = syl.Expand, | |
| gap = 8, | |
| children = { | |
| // Sidebar | |
| syl.box( | |
| width = 200, | |
| gap = 5, | |
| padding = 10, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.text("MENU", wrap = false), | |
| syl.box(id="menu1", padding = 8, background_color = GREEN, children = { syl.text("Dashboard", color = BLACK) }), | |
| syl.box(id="menu2", padding = 8, background_color = GRAY, children = { syl.text("Settings") }), | |
| syl.box(id="menu3", padding = 8, background_color = GRAY, children = { syl.text("Reports") }), | |
| syl.box(id="menu4", padding = 8, background_color = GRAY, children = { syl.text("Logout") }), | |
| } | |
| ), | |
| // Content | |
| syl.box( | |
| sizing = syl.Expand, | |
| gap = 8, | |
| padding = 15, | |
| background_color = [4]u8{ 40, 40, 40, 255 }, | |
| children = { | |
| syl.text("MAIN CONTENT AREA", wrap = false), | |
| syl.box( | |
| layout_direction = .Left_To_Right, | |
| width_sizing = .Expand, | |
| gap = 8, | |
| children = { | |
| syl.box(id="chart1", sizing = syl.Expand, padding = 15, background_color = RED, children = { | |
| syl.text("Chart 1"), | |
| syl.text("Data visualization would go here"), | |
| }), | |
| syl.box(id="chart2", sizing = syl.Expand, padding = 15, background_color = BLUE, children = { | |
| syl.text("Chart 2"), | |
| syl.text("Another visualization"), | |
| }), | |
| } | |
| ), | |
| syl.box( | |
| id="actlog", | |
| width_sizing = .Expand, | |
| padding = 15, | |
| background_color = PURPLE, | |
| children = { | |
| syl.text("ACTIVITY LOG", wrap = false), | |
| syl.text("Recent events and notifications..."), | |
| } | |
| ), | |
| } | |
| ), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| // Demo 7: Advanced Features (Text Wrap & Geometry Animation) | |
| make_advanced_demo :: proc() -> ^syl.Element { | |
| return syl.box( | |
| width_sizing = .Expand, | |
| padding = 15, | |
| gap = 15, | |
| background_color = [4]u8{ 35, 35, 40, 255 }, | |
| border_radius = 4, | |
| children = { | |
| syl.text("ADVANCED FEATURES", wrap = false), | |
| // 1. Text Wrapping Demo | |
| syl.box( | |
| width_sizing = .Expand, | |
| padding = 10, | |
| background_color = [4]u8{ 50, 50, 50, 255 }, | |
| gap = 5, | |
| children = { | |
| syl.text("Text Wrapping Capability:", color = CYAN, wrap = false), | |
| syl.box( | |
| width_sizing = .Expand, | |
| padding = 8, | |
| background_color = BLACK, | |
| children = { | |
| // wrap = true позволяет тексту переноситься | |
| syl.text( | |
| "This is a long text block that demonstrates the automatic wrapping capability. Unlike headers, this text flows to the next line when it hits the container boundary. Resize the window or container to see it adapt!", | |
| color = LIGHT_GRAY, | |
| wrap = true, | |
| ), | |
| } | |
| ), | |
| } | |
| ), | |
| // 2. Geometry Animation (Progress Bar) | |
| syl.box( | |
| width_sizing = .Expand, | |
| padding = 10, | |
| background_color = [4]u8{ 50, 50, 50, 255 }, | |
| gap = 5, | |
| children = { | |
| syl.text("Geometry Animation (Layout Update):", color = CYAN, wrap = false), | |
| // Контейнер бара | |
| syl.box( | |
| id = "progress_track", | |
| width_sizing = .Expand, | |
| height = 30, | |
| // Инлайн стили (вместо StyleSheet) | |
| background_color = [4]u8{ 15, 15, 15, 255 }, | |
| border_color = GRAY, | |
| border_radius = 10, | |
| padding = 3, | |
| children = { | |
| // Сам бар с изменяемой шириной. ID нужен для поиска и обновления. | |
| syl.box( | |
| id = "progress_fill", | |
| width = 10, // Начальная ширина, будет обновляться в цикле | |
| height = 24, | |
| background_color = GREEN, | |
| border_radius = 8, | |
| children = { }, | |
| ), | |
| } | |
| ), | |
| syl.text("Animating width property forces layout recalculation", color = GRAY, wrap = false), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| // --- Logic & Updates --- | |
| // Helper to initialize animated box | |
| init_animated_box :: proc(id: string, original_color, hover_color: [4]u8) { | |
| if id not_in animated_boxes { | |
| animated_boxes[id] = Animated_Box_State{ | |
| is_hovered = false, | |
| original_color = original_color, | |
| hover_color = hover_color, | |
| } | |
| } | |
| } | |
| // Initialize all animated boxes | |
| init_all_animations :: proc() { | |
| // Layout demo | |
| init_animated_box("item1", RED, [4]u8{ 255, 150, 150, 255 }) | |
| init_animated_box("item2", BLUE, [4]u8{ 150, 200, 255, 255 }) | |
| init_animated_box("item3", PURPLE, [4]u8{ 220, 150, 255, 255 }) | |
| init_animated_box("vert1", GREEN, DARK_GREEN) | |
| init_animated_box("vert2", ORANGE, [4]u8{ 255, 200, 100, 255 }) | |
| // Sizing demo | |
| init_animated_box("fit", RED, [4]u8{ 255, 150, 150, 255 }) | |
| init_animated_box("expand", BLUE, CYAN) | |
| init_animated_box("fixed", PURPLE, [4]u8{ 220, 150, 255, 255 }) | |
| // Nested demo | |
| init_animated_box("leftcol", RED, [4]u8{ 255, 150, 150, 255 }) | |
| init_animated_box("rightcol", BLUE, CYAN) | |
| init_animated_box("nest1", [4]u8{ 200, 50, 50, 255 }, [4]u8{ 255, 100, 100, 255 }) | |
| init_animated_box("nest2", [4]u8{ 200, 50, 50, 255 }, [4]u8{ 255, 100, 100, 255 }) | |
| init_animated_box("nestA", [4]u8{ 50, 100, 200, 255 }, CYAN) | |
| init_animated_box("nestB", [4]u8{ 50, 100, 200, 255 }, CYAN) | |
| // Spacing demo | |
| init_animated_box("pad5", GREEN, DARK_GREEN) | |
| init_animated_box("pad15", ORANGE, [4]u8{ 255, 200, 100, 255 }) | |
| init_animated_box("pad25", PURPLE, [4]u8{ 220, 150, 255, 255 }) | |
| // Interactive cards | |
| init_animated_box("card1", GREEN, DARK_GREEN) | |
| init_animated_box("card2", ORANGE, [4]u8{ 255, 200, 100, 255 }) | |
| init_animated_box("card3", RED, [4]u8{ 255, 150, 150, 255 }) | |
| // Dashboard | |
| init_animated_box("status", GREEN, DARK_GREEN) | |
| init_animated_box("users", BLUE, CYAN) | |
| init_animated_box("menu1", GREEN, DARK_GREEN) | |
| init_animated_box("menu2", GRAY, LIGHT_GRAY) | |
| init_animated_box("menu3", GRAY, LIGHT_GRAY) | |
| init_animated_box("menu4", GRAY, [4]u8{ 255, 100, 100, 255 }) | |
| init_animated_box("chart1", RED, [4]u8{ 255, 150, 150, 255 }) | |
| init_animated_box("chart2", BLUE, CYAN) | |
| init_animated_box("actlog", PURPLE, [4]u8{ 220, 150, 255, 255 }) | |
| } | |
| // Update animated box hover state | |
| update_animated_box :: proc(box: ^syl.Box) { | |
| if box.id == "" do return | |
| state, ok := &animated_boxes[box.id] | |
| if !ok do return | |
| // ИСПРАВЛЕНИЕ: Используем Raylib для перевода координат экрана в координаты мира | |
| screen_mouse := rl.GetMousePosition() | |
| world_mouse := rl.GetScreenToWorld2D(screen_mouse, camera.camera) | |
| box_rect := rl.Rectangle{ | |
| box.global_position.x, | |
| box.global_position.y, | |
| box.size.x, | |
| box.size.y | |
| } | |
| // Проверяем коллизию с world_mouse, а не с "сырыми" координатами | |
| collide := rl.CheckCollisionPointRec(world_mouse, box_rect) | |
| if collide && !state.is_hovered { | |
| state.is_hovered = true | |
| syl.animate_color(&box.style.background_color, state.hover_color, 0.2) | |
| } else if !collide && state.is_hovered { | |
| state.is_hovered = false | |
| syl.animate_color(&box.style.background_color, state.original_color, 0.2) | |
| } | |
| } | |
| // Recursive function to update layout animations (Progress Bar) | |
| update_layout_animations :: proc(element: ^syl.Element, progress: f32) { | |
| #partial switch element.type { | |
| case .Box: | |
| box := cast(^syl.Box)element | |
| // Находим элемент прогресс-бара по ID и обновляем его ширину | |
| if box.id == "progress_fill" { | |
| // Максимальная ширина контейнера примерно 450px (зависит от родителя) | |
| // Анимируем ширину от 0 до 450 | |
| target_width := 450.0 * progress | |
| // Обновляем ширину в стиле (предполагаем, что библиотека хранит width в style) | |
| // Если библиотека хранит width напрямую в структуре Box, здесь может потребоваться box.width = target_width | |
| // Но обычно в таких либах это box.style.width | |
| box.style.width = target_width | |
| // Также меняем цвет в зависимости от прогресса | |
| if progress > 0.8 { | |
| box.style.background_color = RED | |
| } else { | |
| box.style.background_color = GREEN | |
| } | |
| } | |
| for child in box.children { | |
| update_layout_animations(child, progress) | |
| } | |
| } | |
| } | |
| // Recursively update all boxes (Hover logic) | |
| update_all_boxes :: proc(element: ^syl.Element) { | |
| #partial switch element.type { | |
| case .Box: | |
| box := cast(^syl.Box)element | |
| update_animated_box(box) | |
| for child in box.children { | |
| update_all_boxes(child) | |
| } | |
| } | |
| } | |
| make_app :: proc() -> ^syl.Element { | |
| return syl.box( | |
| size = { SCREEN_W, SCREEN_H + 600 }, // Increased height for scrolling | |
| padding = 20, | |
| gap = 15, | |
| style_sheet = &style_sheet, | |
| children = { | |
| // Header | |
| syl.box( | |
| width_sizing = .Expand, | |
| padding = 15, | |
| background_color = DARK_BG, | |
| layout_direction = .Left_To_Right, | |
| gap = 10, | |
| children = { | |
| syl.text("SYL UI LIBRARY", wrap = false), | |
| syl.box(sizing = syl.Expand, border_color = BLANK), | |
| syl.text("Interactive Demo v2.0", wrap = false), | |
| } | |
| ), | |
| syl.text("Hover over elements to see animations! Drag with LEFT mouse button to scroll.", wrap = false), | |
| // Main content - two columns | |
| syl.box( | |
| sizing = syl.Expand, | |
| layout_direction = .Left_To_Right, | |
| gap = 15, | |
| children = { | |
| // Left column | |
| syl.box( | |
| width = 500, | |
| gap = 15, | |
| children = { | |
| make_layout_demo(), | |
| make_sizing_demo(), | |
| make_spacing_demo(), | |
| make_interactive_demo(), | |
| } | |
| ), | |
| // Right column | |
| syl.box( | |
| sizing = syl.Expand, | |
| gap = 15, | |
| children = { | |
| make_nested_demo(), | |
| make_dashboard_demo(), | |
| make_advanced_demo(), // New demo section | |
| } | |
| ), | |
| } | |
| ), | |
| } | |
| ) | |
| } | |
| main :: proc() { | |
| rl.InitWindow(SCREEN_W, SCREEN_H, "Syl UI Library - Extended Demo") | |
| rl.SetTargetFPS(60) | |
| camera.camera = rl.Camera2D{ | |
| offset = { 0, 0 }, | |
| target = { 0, 0 }, | |
| rotation = 0, | |
| zoom = 1.0, | |
| } | |
| animated_boxes = make(map[string]Animated_Box_State) | |
| init_all_animations() | |
| app := make_app() | |
| syl.calculate_layout(app) | |
| for !rl.WindowShouldClose() { | |
| // --- Input Handling --- | |
| // Handle camera dragging with LEFT mouse button | |
| if rl.IsMouseButtonPressed(.LEFT) { | |
| camera.dragging = true | |
| camera.last_mouse_pos = rl.GetMousePosition() | |
| } | |
| if rl.IsMouseButtonReleased(.LEFT) { | |
| camera.dragging = false | |
| } | |
| if camera.dragging { | |
| current_mouse := rl.GetMousePosition() | |
| delta := rl.Vector2{ | |
| current_mouse.x - camera.last_mouse_pos.x, | |
| current_mouse.y - camera.last_mouse_pos.y, | |
| } | |
| camera.camera.target.x -= delta.x | |
| camera.camera.target.y -= delta.y | |
| // Clamp camera offset | |
| camera.camera.target.y = max(camera.camera.target.y, 0) | |
| camera.camera.target.y = min(camera.camera.target.y, SCREEN_H + 600 - SCREEN_H) | |
| camera.last_mouse_pos = current_mouse | |
| } | |
| // Mouse wheel scrolling | |
| wheel := rl.GetMouseWheelMove() | |
| if wheel != 0 { | |
| camera.camera.target.y -= wheel * 30 | |
| camera.camera.target.y = max(camera.camera.target.y, 0) | |
| camera.camera.target.y = min(camera.camera.target.y, SCREEN_H + 600 - SCREEN_H) | |
| } | |
| // --- Logic Updates --- | |
| // Update Progress Bar State | |
| progress_value += 0.01 * progress_dir | |
| if progress_value >= 1.0 { | |
| progress_value = 1.0 | |
| progress_dir = -1.0 | |
| } else if progress_value <= 0.0 { | |
| progress_value = 0.0 | |
| progress_dir = 1.0 | |
| } | |
| // Apply updates to the UI tree | |
| update_layout_animations(app, progress_value) // Update geometry (width) | |
| syl.calculate_layout(app) // Recalculate positions based on new width | |
| update_all_boxes(app) // Update hover states | |
| syl.update_transitions() // Update color transitions | |
| // --- Rendering --- | |
| rl.BeginDrawing() | |
| rl.ClearBackground(cast(rl.Color)BLACK) | |
| // Apply camera transform | |
| rl.BeginMode2D(camera.camera) | |
| renderer.draw(app) | |
| rl.EndMode2D() | |
| // Draw scrollbar indicator | |
| total_content_h := f32(SCREEN_H + 600) | |
| if total_content_h > SCREEN_H { | |
| visible_ratio := f32(SCREEN_H) / total_content_h | |
| scrollbar_height := f32(SCREEN_H) * visible_ratio | |
| scroll_pos := (camera.camera.target.y / (total_content_h - f32(SCREEN_H))) * (f32(SCREEN_H) - scrollbar_height) | |
| rl.DrawRectangle(i32(SCREEN_W - 10), i32(scroll_pos), 8, i32(scrollbar_height), cast(rl.Color)GREEN) | |
| } | |
| rl.EndDrawing() | |
| } | |
| delete(animated_boxes) | |
| rl.CloseWindow() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment