Skip to content

Instantly share code, notes, and snippets.

@Hammer2900
Last active January 18, 2026 10:11
Show Gist options
  • Select an option

  • Save Hammer2900/186c2cddd1226347e3559fb312ca8f3d to your computer and use it in GitHub Desktop.

Select an option

Save Hammer2900/186c2cddd1226347e3559fb312ca8f3d to your computer and use it in GitHub Desktop.
torent client gui on odin, place to example folder
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()
}
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