diff --git a/doc/assets/Capture-2026-02-06-232336.png b/doc/assets/Capture-2026-02-06-232336.png new file mode 100644 index 0000000..773ba1e Binary files /dev/null and b/doc/assets/Capture-2026-02-06-232336.png differ diff --git a/src/boot.asm b/src/boot.asm index cc47b1b..4cadbcf 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -195,17 +195,30 @@ build_interface: mov word [gs:si + widget.x], 10 mov word [gs:si + widget.y], 100 mov word [gs:si + widget.w], 150 - mov word [gs:si + widget.h], 20 - mov word [gs:si + widget.text_ofs], str_drag - mov word [gs:si + widget.text_seg], cs + mov word [gs:si + widget.h], 12 mov byte [gs:si + widget.type], WIDGET_TYPE_SLIDER - mov byte [gs:si + widget.attr_mode], 1 ; Horizontal + mov byte [gs:si + widget.attr_mode], SLIDER_HORIZONTAL mov word [gs:si + widget.attr_min], 10 ; X Min mov word [gs:si + widget.attr_max], 140 ; X Max (Widget.x + W - ThumbW) mov word [gs:si + widget.attr_val], 10 ; Position initiale mov byte [gs:si + widget.thumb_pct], 15 ; Curseur fait 15% de la largeur ; mov word [gs:si + widget.event_drag], on_drag_slider +; Créer Slider (Drag) + call gui_alloc_widget + jc .mem_full + mov word [gs:si + widget.x], 400 + mov word [gs:si + widget.y], 10 + mov word [gs:si + widget.w], 16 + mov word [gs:si + widget.h], 150 + mov byte [gs:si + widget.type], WIDGET_TYPE_SLIDER + mov byte [gs:si + widget.attr_mode], SLIDER_VERTICAL + mov word [gs:si + widget.attr_min], 10 ; X Min + mov word [gs:si + widget.attr_max], 140 ; X Max (Widget.x + W - ThumbW) + mov word [gs:si + widget.attr_val], 140 ; Position initiale + mov byte [gs:si + widget.thumb_pct], 25 ; Curseur fait 15% de la largeur +; mov word [gs:si + widget.event_drag], on_drag_slider + .mem_full: ret @@ -216,7 +229,6 @@ str_hello db "Hello", 0 str_option1 db "option 1", 0 str_option2 db "option 2", 0 str_option3 db "option 3", 0 -str_drag db "->", 0 ; ----------------------------------------------------------- ; timer ISR (IRQ -> INT) diff --git a/src/drivers/gfx_cgam.asm b/src/drivers/gfx_cgam.asm index d59db9f..b373ef6 100644 --- a/src/drivers/gfx_cgam.asm +++ b/src/drivers/gfx_cgam.asm @@ -71,15 +71,13 @@ %define TXT_MODE 4 %define PUTCH 5 %define WRITE 6 -%define LINE_VERT 7 -%define LINE_HORIZ 8 -%define RECTANGLE 9 -%define RECTANGLE_FILL 10 -%define RECTANGLE_ROUND 11 -%define MOUSE_HIDE 12 -%define MOUSE_SHOW 13 -%define MOUSE_MOVE 14 -%define LINE 15 +%define LINE 7 +%define RECTANGLE 8 +%define RECTANGLE_FILL 9 +%define RECTANGLE_ROUND 10 +%define MOUSE_HIDE 11 +%define MOUSE_SHOW 12 +%define MOUSE_MOVE 13 ; ------------------------------------------------------------ ; COMMENTAIRE SUR LA MÉTHODE D'APPEL @@ -100,15 +98,14 @@ graph_driver: dw cga_set_writemode ; mode texte dw cga_putc ; dessin d'un caractère dw cga_write ; dessin d'une chaine de caractère - dw cga_line_vertical ; dessin d'une ligne verticale - dw cga_line_horizontal ; dessin d'une ligne horizontale + dw cga_line ; dessin d'une ligne (Bresenham) avec décision horizontale/verticale dw cga_draw_rect ; dessin d'un rectangle dw cga_fill_rect ; dessin d'un rectangle plein dw cga_draw_rounded_frame ; dessin d'un rectangle arrondi dw cga_mouse_hide ; déplacement du curseur dw cga_mouse_show ; déplacement du curseur dw cga_mouse_cursor_move ; déplacement du curseur - dw cga_line ; dessin d'une ligne (Bresenham) + ; ============================================================================= ; SECTION : PATTERNS & FILL @@ -790,6 +787,7 @@ cga_line_horizontal: %undef .x1 %undef .x2 %undef .y + ; ------------------------------------------------------------ ; cga_line (x1, y1, x2, y2, color) ; Algorithme de Bresenham pour tracer une ligne arbitraire @@ -803,6 +801,8 @@ cga_line_horizontal: cga_line: push bp mov bp, sp + pusha ; Sauvegarde de tous les registres (AX, BX, CX, DX, SI, DI) + push es ; Sauvegarde ES ; --- Optimisation : Lignes H/V --- mov ax, .y1 @@ -812,15 +812,24 @@ cga_line: cmp ax, .x2 je .do_vert - sub sp, 10 ; Variables locales: dx, dy, sx, sy, err - pusha - push es - + ; --- Bresenham --- mov ax, VIDEO_SEG mov es, ax call cga_mouse_hide + sub sp, 10 ; Variables locales: dx, dy, sx, sy, err + + ; --- Definitions locales --- + ; BP pointe sur l'ancien BP. + ; PUSHA (16 octets) + PUSH ES (2 octets) = 18 octets + ; Les variables locales commencent donc à BP-18 + %define .dx word [bp-20] + %define .dy word [bp-22] + %define .sx word [bp-24] + %define .sy word [bp-26] + %define .err word [bp-28] + ; --- Initialisation Bresenham --- ; dx = abs(x2 - x1), sx = sign(x2 - x1) mov ax, .x2 @@ -830,8 +839,8 @@ cga_line: neg ax neg bx ; sx = -1 .calc_dx: - mov [bp-2], ax ; save dx - mov [bp-6], bx ; save sx + mov .dx, ax ; save dx + mov .sx, bx ; save sx ; dy = abs(y2 - y1), sy = sign(y2 - y1) mov ax, .y2 @@ -841,13 +850,13 @@ cga_line: neg ax neg bx ; sy = -1 .calc_dy: - mov [bp-4], ax ; save dy - mov [bp-8], bx ; save sy + mov .dy, ax ; save dy + mov .sy, bx ; save sy ; err = dx - dy - mov ax, [bp-2] ; dx - sub ax, [bp-4] ; dy - mov [bp-10], ax ; err + mov ax, .dx ; dx + sub ax, .dy ; dy + mov .err, ax ; err ; Coordonnées courantes mov cx, .x1 @@ -878,32 +887,30 @@ cga_line: je .done .step: - mov ax, [bp-10] ; e2 = err + mov ax, .err ; e2 = err shl ax, 1 ; e2 = 2*err - mov bx, [bp-4] ; dy + mov bx, .dy ; dy neg bx ; -dy cmp ax, bx jle .check_y - add word [bp-10], bx ; err += -dy - add cx, [bp-6] ; x += sx + add .err, bx ; err += -dy + add cx, .sx ; x += sx .check_y: - mov bx, [bp-2] ; dx + mov bx, .dx ; dx cmp ax, bx jge .loop ; if e2 >= dx, skip y step - add word [bp-10], bx ; err += dx - add dx, [bp-8] ; y += sy + add .err, bx ; err += dx + add dx, .sy ; y += sy jmp .loop .done: call cga_mouse_show - pop es - popa - leave - ret + add sp, 10 ; Libération variables locales + jmp .exit .do_horiz: push word [bp+12] ; color @@ -912,8 +919,7 @@ cga_line: push word [bp+4] ; x1 call cga_line_horizontal add sp, 8 - pop bp - ret + jmp .exit .do_vert: push word [bp+12] ; color @@ -922,13 +928,23 @@ cga_line: push word [bp+4] ; x call cga_line_vertical add sp, 8 + +.exit: + pop es + popa pop bp ret + %undef .x1 %undef .y1 %undef .x2 %undef .y2 %undef .color +%undef .dx +%undef .dy +%undef .sx +%undef .sy +%undef .err ; ------------------------------------------------------------ ; cga_draw_rect diff --git a/src/services/gui_lib.asm b/src/services/gui_lib.asm index b0c8fd7..4593f95 100644 --- a/src/services/gui_lib.asm +++ b/src/services/gui_lib.asm @@ -20,6 +20,10 @@ ; ; ============================================================================= +; +; https://github.com/buserror/libmui +; + ; --- Configuration --- %define GUI_RAM_SEG 0x0A00 ; Segment de données UI %define GUI_MAX_WIDGETS 32 ; Nombre max de widgets simultanés @@ -44,6 +48,9 @@ ; --- Widgets attributs --- %define BUTTON_OK 1 +%define SLIDER_HORIZONTAL 1 +%define SLIDER_VERTICAL 2 + ; --- Structure d'un OBJET (Bouton, etc) --- struc widget .state resb 1 ; État (0=libre, >0=utilisé) @@ -371,55 +378,157 @@ gui_logic_slider: mov byte [gs:si + widget.state], GUI_STATE_PRESSED - ; Calcul de l'ancrage + ; --- CALCUL TAILLE THUMB POUR HIT TEST --- + ; On doit savoir si le clic est SUR le curseur ou SUR la piste + push ax + push bx + push dx + + xor ax, ax + xor bx, bx + + cmp byte [gs:si + widget.attr_mode], 2 + je .calc_v + + ; --- Horizontal --- + mov ax, [gs:si + widget.w] + mov bl, [gs:si + widget.thumb_pct] + mul bx + mov bx, 100 + div bx ; AX = Thumb Width + + ; Check collision avec le thumb actuel + mov bx, [gs:si + widget.attr_val] + cmp cx, bx + jb .click_outside_h ; Click avant le thumb + add bx, ax + cmp cx, bx + ja .click_outside_h ; Click après le thumb + + ; Click DANS le thumb : Anchor = MouseX - AttrVal mov ax, cx sub ax, [gs:si + widget.attr_val] mov [gs:si + widget.attr_anchor], ax - cmp byte [gs:si + widget.attr_mode], 2 - jne .do_drag + jmp .init_done + + .click_outside_h: + ; Click HORS du thumb : On centre le thumb sur la souris + shr ax, 1 + mov [gs:si + widget.attr_anchor], ax + jmp .init_done + + .calc_v: + ; --- Vertical --- + mov ax, [gs:si + widget.h] + mov bl, [gs:si + widget.thumb_pct] + mul bx + mov bx, 100 + div bx ; AX = Thumb Height + + ; Check collision avec le thumb actuel + mov bx, [gs:si + widget.attr_val] + cmp dx, bx + jb .click_outside_v + add bx, ax + cmp dx, bx + ja .click_outside_v + + ; Click DANS le thumb mov ax, dx sub ax, [gs:si + widget.attr_val] mov [gs:si + widget.attr_anchor], ax + jmp .init_done + + .click_outside_v: + ; Click HORS du thumb + shr ax, 1 + mov [gs:si + widget.attr_anchor], ax + + .init_done: + pop dx + pop bx + pop ax .do_drag: ; --- LOGIQUE DE DÉPLACEMENT --- + ; On calcule les limites dynamiquement pour éviter que le curseur ne sorte + push ax + push bx + push dx + + xor ax, ax + xor bx, bx + cmp byte [gs:si + widget.attr_mode], 1 je .drag_h cmp byte [gs:si + widget.attr_mode], 2 je .drag_v + + pop dx + pop bx + pop ax xor ax, ax ret .drag_h: + ; 1. Thumb Width -> AX + mov ax, [gs:si + widget.w] + mov bl, [gs:si + widget.thumb_pct] + mul bx + mov bx, 100 + div bx ; AX = Thumb Width + + ; 2. Max Pos = X + W - ThumbWidth + mov bx, [gs:si + widget.x] + add bx, [gs:si + widget.w] + sub bx, ax ; BX = Max Pos + + ; 3. Min Pos = X + mov dx, [gs:si + widget.x] ; DX = Min Pos + + ; 4. Target Pos = MouseX - Anchor mov ax, cx - sub ax, [gs:si + widget.attr_anchor] ; Nouvelle pos = MouseX - Anchor - ; Clamp Min - cmp ax, [gs:si + widget.attr_min] - jge .chk_max_h - mov ax, [gs:si + widget.attr_min] - .chk_max_h: - ; Clamp Max - cmp ax, [gs:si + widget.attr_max] - jle .apply_pos - mov ax, [gs:si + widget.attr_max] - jmp .apply_pos + sub ax, [gs:si + widget.attr_anchor] + jmp .apply_clamp .drag_v: - mov ax, dx - sub ax, [gs:si + widget.attr_anchor] - ; Clamp Min - cmp ax, [gs:si + widget.attr_min] - jge .chk_max_v - mov ax, [gs:si + widget.attr_min] + ; 1. Thumb Height -> AX + mov ax, [gs:si + widget.h] + mov bl, [gs:si + widget.thumb_pct] + mul bx + mov bx, 100 + div bx ; AX = Thumb Height - .chk_max_v: - ; Clamp Max - cmp ax, [gs:si + widget.attr_max] - jle .apply_pos - mov ax, [gs:si + widget.attr_max] + ; 2. Max Pos = Y + H - ThumbHeight + mov bx, [gs:si + widget.y] + add bx, [gs:si + widget.h] + sub bx, ax ; BX = Max Pos + + ; 3. Min Pos = Y + mov dx, [gs:si + widget.y] ; DX = Min Pos + + ; 4. Target Pos = MouseY - Anchor + ; MouseY est sur la pile (push dx initial), on le recupere via SP + mov bp, sp + mov ax, [bp] ; [bp] = Saved DX (MouseY) + sub ax, [gs:si + widget.attr_anchor] + + .apply_clamp: + ; AX = Target, DX = Min, BX = Max + cmp ax, dx + jge .chk_max + mov ax, dx jmp .apply_pos + .chk_max: + cmp ax, bx + jle .apply_pos + mov ax, bx .apply_pos: + pop dx + pop bx + add sp, 2 ; Clean AX from stack + cmp ax, [gs:si + widget.attr_val] je .no_change mov [gs:si + widget.attr_val], ax @@ -471,7 +580,6 @@ gui_logic_checkbox: ret - ; Dessine le widget pointé par SI gui_draw_single_widget: pusha @@ -525,23 +633,36 @@ gui_draw_single_widget: .case_5: - .done: GFX MOUSE_SHOW ; On réaffiche la souris APRES, capturant le widget fini popa ret +; --- Locals Mapping --- +%define .thumb_x1 word [bp-2] +%define .thumb_y1 word [bp-4] +%define .thumb_x2 word [bp-6] +%define .thumb_y2 word [bp-8] +%define .track_x1 word [bp-10] +%define .track_y1 word [bp-12] +%define .track_x2 word [bp-14] +%define .track_y2 word [bp-16] draw_slider: + push bp + mov bp, sp + sub sp, 16 ; Reserve space for locals + + ; Save track coords (Arguments passed in AX, BX, CX, DX) + mov .track_x1, ax + mov .track_y1, bx + mov .track_x2, cx + mov .track_y2, dx + ; 1. Dessiner la piste (Track) - GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE_LIGHT - GFX RECTANGLE, ax, bx, cx, dx, 0 + GFX RECTANGLE_FILL, .track_x1, .track_y1, .track_x2, .track_y2, PATTERN_WHITE_LIGHT + GFX RECTANGLE, .track_x1, .track_y1, .track_x2, .track_y2, 0 ; 2. Calculer la taille du curseur (Thumb) - push ax ; Sauvegarde coords track (X1, Y1, X2, Y2) - push bx - push cx - push dx - cmp byte [gs:si + widget.attr_mode], 2 je .calc_v @@ -553,12 +674,34 @@ draw_slider: mov bx, 100 div bx ; AX = Thumb Width - mov bx, [gs:si + widget.y] ; BX = Y1 - mov dx, bx - add dx, [gs:si + widget.h] ; DX = Y2 - mov cx, [gs:si + widget.attr_val] ; CX = X1 - add ax, cx ; AX = X1 + Width = X2 - xchg ax, cx ; AX = X1, CX = X2 + ; Thumb X1 = attr_val + mov cx, [gs:si + widget.attr_val] + + ; --- CLAMP X1 (Fix Overflow) --- + ; Max X1 = TrackX2 - ThumbW + mov dx, .track_x2 + sub dx, ax ; DX = Max X1 + cmp cx, dx + jle .chk_min_x + mov cx, dx + .chk_min_x: + cmp cx, .track_x1 + jge .ok_x1 + mov cx, .track_x1 + .ok_x1: + mov .thumb_x1, cx + + ; Thumb X2 = X1 + Width + add cx, ax + mov .thumb_x2, cx + + ; Thumb Y1 = Track Y1 + mov ax, .track_y1 + mov .thumb_y1, ax + + ; Thumb Y2 = Track Y2 + mov ax, .track_y2 + mov .thumb_y2, ax jmp .draw_thumb .calc_v: @@ -570,23 +713,119 @@ draw_slider: mov bx, 100 div bx ; AX = Thumb Height - mov bx, [gs:si + widget.attr_val] ; BX = Y1 - mov dx, bx - add dx, ax ; DX = Y2 - mov ax, [gs:si + widget.x] ; AX = X1 - mov cx, ax - add cx, [gs:si + widget.w] ; CX = X2 + ; Thumb Y1 = attr_val + mov cx, [gs:si + widget.attr_val] + + ; --- CLAMP Y1 (Fix Overflow) --- + ; Max Y1 = TrackY2 - ThumbH + mov dx, .track_y2 + sub dx, ax ; DX = Max Y1 + cmp cx, dx + jle .chk_min_y + mov cx, dx + .chk_min_y: + cmp cx, .track_y1 + jge .ok_y1 + mov cx, .track_y1 + .ok_y1: + mov .thumb_y1, cx + + ; Thumb Y2 = Y1 + Height + add cx, ax + mov .thumb_y2, cx + + ; Thumb X1 = Track X1 + mov ax, .track_x1 + mov .thumb_x1, ax + + ; Thumb X2 = Track X2 + mov ax, .track_x2 + mov .thumb_x2, ax .draw_thumb: ; 3. Dessiner le curseur (Thumb) - GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE - GFX RECTANGLE, ax, bx, cx, dx, 0 - add sp, 8 ; Nettoyer la pile des push ax..dx - GFX TXT_MODE, GFX_TXT_BLACK_TRANSPARENT - call draw_text + GFX RECTANGLE_FILL, .thumb_x1, .thumb_y1, .thumb_x2, .thumb_y2, PATTERN_WHITE + GFX RECTANGLE, .thumb_x1, .thumb_y1, .thumb_x2, .thumb_y2, 0 + + ; --- Grip Lines (3 lignes pour un look moderne) --- + cmp byte [gs:si + widget.attr_mode], 2 + je .grip_v + + ; --- Grip Horizontal (Lignes verticales) --- + ; Center X + mov ax, .thumb_x1 + add ax, .thumb_x2 + shr ax, 1 ; AX = Center X + + ; Y bounds (padding 3px) + mov bx, .thumb_y1 + add bx, 3 + mov dx, .thumb_y2 + sub dx, 3 + + ; Center Line + GFX LINE, ax, bx, ax, dx, 0 + + ; Left Line + mov cx, ax + sub cx, 2 + cmp cx, .thumb_x1 + jle .skip_left + GFX LINE, cx, bx, cx, dx, 0 + .skip_left: + + ; Right Line + mov cx, ax + add cx, 2 + cmp cx, .thumb_x2 + jge .grip_done + GFX LINE, cx, bx, cx, dx, 0 + jmp .grip_done + + .grip_v: + ; --- Grip Vertical (Lignes horizontales) --- + ; Center Y + mov bx, .thumb_y1 + add bx, .thumb_y2 + shr bx, 1 ; BX = Center Y + + ; X bounds (padding 3px) + mov ax, .thumb_x1 + add ax, 3 + mov cx, .thumb_x2 + sub cx, 3 + + ; Center Line + GFX LINE, ax, bx, cx, bx, 0 + + ; Top Line + mov dx, bx + sub dx, 2 + cmp dx, .thumb_y1 + jle .skip_top + GFX LINE, ax, dx, cx, dx, 0 + .skip_top: + + ; Bottom Line + mov dx, bx + add dx, 2 + cmp dx, .thumb_y2 + jge .grip_done + GFX LINE, ax, dx, cx, dx, 0 + + .grip_done: + leave ret - +; Clean up defines +%undef .thumb_x1 +%undef .thumb_y1 +%undef .thumb_x2 +%undef .thumb_y2 +%undef .track_x1 +%undef .track_y1 +%undef .track_x2 +%undef .track_y2 draw_button: ; Dispatch selon état