808 lines
No EOL
21 KiB
NASM
808 lines
No EOL
21 KiB
NASM
; =============================================================================
|
|
; Project : Custom BIOS / ROM
|
|
; File : gui_lib.asm
|
|
; Author : frater
|
|
;
|
|
; License : GNU General Public License v3.0 or later (GPL-3.0+)
|
|
;
|
|
; This program is free software: you can redistribute it and/or modify
|
|
; it under the terms of the GNU General Public License as published by
|
|
; the Free Software Foundation, either version 3 of the License, or
|
|
; (at your option) any later version.
|
|
;
|
|
; This program is distributed in the hope that it will be useful,
|
|
; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
; GNU General Public License for more details.
|
|
;
|
|
; You should have received a copy of the GNU General Public License
|
|
; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
;
|
|
; =============================================================================
|
|
|
|
; --- Configuration ---
|
|
%define GUI_RAM_SEG 0x0A00 ; Segment de données UI
|
|
%define GUI_MAX_WIDGETS 32 ; Nombre max de widgets simultanés
|
|
|
|
; Dimensions
|
|
%define GUI_CHECKBOX_SIZE 10 ;
|
|
|
|
; --- Drapeaux & États ---
|
|
%define GUI_STATE_FREE 0 ; Le slot est vide (mémoire dispo)
|
|
%define GUI_STATE_NORMAL 1 ; Affiché, repos
|
|
%define GUI_STATE_HOVER 2 ; Souris dessus
|
|
%define GUI_STATE_PRESSED 3 ; Clic enfoncé
|
|
%define GUI_STATE_DISABLED 4 ; Grisé
|
|
|
|
; --- Types de Widgets ---
|
|
%define WIDGET_TYPE_BUTTON 0
|
|
%define WIDGET_TYPE_SLIDER 1
|
|
%define WIDGET_TYPE_LABEL 2
|
|
%define WIDGET_TYPE_ROUND_BUTTON 3
|
|
%define WIDGET_TYPE_CHECKBOX 4
|
|
|
|
; --- Widgets attributs ---
|
|
%define BUTTON_OK 1
|
|
|
|
; --- Structure d'un OBJET (Bouton, etc) ---
|
|
struc widget
|
|
.state resb 1 ; État (0=libre, >0=utilisé)
|
|
.type resb 1 ; Type (0=Button, 1=Slider...)
|
|
.oldstate resb 1 ; widget a-t-il été modifié ?
|
|
.user_id resb 1 ; ID unique utilisateur
|
|
.x resw 1 ; Position X
|
|
.y resw 1 ; Position Y
|
|
.x2 resw 1 ; Position X2 calculée
|
|
.y2 resw 1 ; Position Y2 calculée
|
|
.w resw 1 ; Largeur
|
|
.h resw 1 ; Hauteur
|
|
.text_ofs resw 1 ; Offset du texte
|
|
.text_seg resw 1 ; Segment du texte
|
|
.event_click resw 1 ; adresse de la fonction "on click"
|
|
; .event_hover resw 1 ; adresse de la fonction "on hover"
|
|
; .event_release resw 1 ; adresse de la fonction "on release"
|
|
; .event_disable resw 1 ; adresse de la fonction "on disable"
|
|
; .event_enable resw 1 ; adresse de la fonction "on enable"
|
|
.event_drag resw 1 ; adresse de la fonction "on drag"
|
|
|
|
.attr_mode resb 1 ; 0=None, 1=Horiz, 2=Vert
|
|
.attr_min resw 1 ; Valeur/Position Min
|
|
.attr_max resw 1 ; Valeur/Position Max
|
|
.attr_val resw 1 ; Valeur/Position Actuelle
|
|
.attr_anchor resw 1 ; Offset interne pour le drag
|
|
.thumb_pct resb 1 ; Taille du curseur en % (1-100)
|
|
|
|
alignb 2 ; Alignement mémoire pour performance
|
|
endstruc
|
|
|
|
; =============================================================================
|
|
; SECTION : GESTION MÉMOIRE (ALLOCATION / LIBÉRATION)
|
|
; =============================================================================
|
|
|
|
; -----------------------------------------------------------------------------
|
|
; gui_init_system
|
|
; Initialise toute la mémoire des widgets à 0 (Libre)
|
|
; Entrée : DS doit pointer vers GUI_RAM_SEG
|
|
; -----------------------------------------------------------------------------
|
|
gui_init_system:
|
|
push eax
|
|
push cx
|
|
push es
|
|
push di
|
|
|
|
mov ax, GUI_RAM_SEG
|
|
mov es, ax
|
|
|
|
xor di, di
|
|
mov cx, (widget_size * GUI_MAX_WIDGETS) / 2
|
|
xor ax, ax
|
|
rep stosw ; Plus sûr en mode 16 bits pour l'alignement
|
|
|
|
pop di
|
|
pop es
|
|
pop cx
|
|
pop eax
|
|
ret
|
|
|
|
; -----------------------------------------------------------------------------
|
|
; gui_alloc_widget
|
|
; Cherche un slot vide et retourne son adresse
|
|
; Sortie :
|
|
; - Succès : CF=0 (Carry Clear), GS:SI = Offset du widget
|
|
; - Echec : CF=1 (Carry Set), GS:SI = Indéfini (ou 0)
|
|
; -----------------------------------------------------------------------------
|
|
gui_alloc_widget:
|
|
push ax
|
|
push cx
|
|
push bx
|
|
|
|
mov ax, GUI_RAM_SEG
|
|
mov gs, ax
|
|
|
|
mov si, 0 ; Début du pool
|
|
mov cx, GUI_MAX_WIDGETS
|
|
|
|
.loop_find:
|
|
cmp byte [gs:si + widget.state], GUI_STATE_FREE
|
|
je .found ; Si state == 0, c'est libre !
|
|
|
|
add si, widget_size ; Sinon on passe au suivant
|
|
loop .loop_find
|
|
|
|
; Pas trouvé (Plein)
|
|
stc ; set Carry Flag : erreur
|
|
jmp .done
|
|
|
|
.found:
|
|
; On initialise le slot trouvé
|
|
mov byte [gs:si + widget.state], GUI_STATE_NORMAL ; Marquer comme occupé
|
|
mov byte [gs:si + widget.type], WIDGET_TYPE_BUTTON ; Type par défaut
|
|
mov byte [gs:si + widget.oldstate], 0 ; doit etre dessiné
|
|
|
|
; Reset des champs critiques pour éviter les déchets
|
|
mov word [gs:si + widget.event_click], 0
|
|
mov word [gs:si + widget.event_drag], 0
|
|
mov byte [gs:si + widget.thumb_pct], 10 ; 10% par défaut
|
|
|
|
clc ; Clear Carry Flag
|
|
.done:
|
|
pop bx
|
|
pop cx
|
|
pop ax
|
|
ret
|
|
|
|
; -----------------------------------------------------------------------------
|
|
; gui_free_widget
|
|
; Libère un widget
|
|
; Entrée : SI = Pointeur widget
|
|
; -----------------------------------------------------------------------------
|
|
gui_free_widget:
|
|
push eax
|
|
push es
|
|
|
|
mov ax, GUI_RAM_SEG
|
|
mov es, ax
|
|
|
|
mov di, si
|
|
mov cx, (widget_size)
|
|
shr cx, 2 ; cx / 4
|
|
xor eax, eax
|
|
rep stosd ; Remplit tout de 0
|
|
|
|
mov byte [es:si + widget.state], GUI_STATE_FREE
|
|
; On efface visuellement le widget (optionnel, remplit de blanc)
|
|
; Pour l'instant on le marque juste libre, il sera redessiné par-dessus.
|
|
|
|
pop es
|
|
pop eax
|
|
ret
|
|
|
|
; =============================================================================
|
|
; SECTION : MOTEUR D'ÉVÉNEMENTS (EVENT LOOP)
|
|
; =============================================================================
|
|
|
|
; -----------------------------------------------------------------------------
|
|
; gui_process_all
|
|
; 1. Redessine tous les widgets actifs
|
|
; 2. Vérifie les clics et appelle les callbacks
|
|
; Entrée :
|
|
; CX = Mouse X
|
|
; DX = Mouse Y
|
|
; BL = Mouse Buttons
|
|
; -----------------------------------------------------------------------------
|
|
gui_process_all:
|
|
pusha
|
|
push ds
|
|
push gs
|
|
|
|
mov ax, GUI_RAM_SEG
|
|
mov gs, ax
|
|
|
|
; Charger l'état de la souris pour toute la passe
|
|
push ds
|
|
mov ax, BDA_DATA_SEG
|
|
mov ds, ax
|
|
mov bl, [BDA_MOUSE + mouse.status]
|
|
pop ds
|
|
|
|
mov si, 0 ; Pointeur début tableau
|
|
mov di, GUI_MAX_WIDGETS ; Compteur
|
|
|
|
.loop_widgets:
|
|
; Est-ce que ce slot est occupé ?
|
|
cmp byte [gs:si + widget.state], GUI_STATE_FREE
|
|
je .next_widget
|
|
|
|
; --- Logique Widget ---
|
|
push bx ; Sauver état boutons souris
|
|
call gui_update_logic ; Vérifier collision/clic
|
|
|
|
; Si AL=1 (Clic relâché validé), exécuter le callback
|
|
cmp al, 1
|
|
jne .no_action
|
|
|
|
; Vérifier si un callback est défini
|
|
cmp word [gs:si + widget.event_click], 0
|
|
je .no_action
|
|
|
|
; APPEL DU CALLBACK (Fonction utilisateur)
|
|
; On sauve les registres car le callback peut faire n'importe quoi
|
|
pusha
|
|
call word [gs:si + widget.event_click]
|
|
popa
|
|
|
|
.no_action:
|
|
pop bx ; Restaurer boutons
|
|
|
|
mov al, byte [gs:si + widget.oldstate]
|
|
mov ah, byte [gs:si + widget.state]
|
|
|
|
; Dessiner (si l'état a changé ou pour refresh)
|
|
|
|
cmp al,ah
|
|
je .next_widget ; Si != 0, le widget est à jour, on passe.
|
|
call gui_draw_single_widget
|
|
|
|
.next_widget:
|
|
add si, widget_size
|
|
dec di
|
|
jnz .loop_widgets
|
|
|
|
pop gs
|
|
pop ds
|
|
popa
|
|
ret
|
|
|
|
; =============================================================================
|
|
; SECTION : LOGIQUE INTERNE ET DESSIN
|
|
; =============================================================================
|
|
|
|
; (Interne) Met à jour l'état d'un seul widget
|
|
; Entrée : SI=Widget, CX=MouseX, DX=MouseY, BL=Buttons
|
|
; Sortie : AL=1 si Clicked, 0 sinon. Met à jour [gs:si].state
|
|
gui_update_logic:
|
|
push ds
|
|
mov ax, BDA_DATA_SEG
|
|
mov ds, ax
|
|
|
|
mov al, byte [gs:si + widget.state]
|
|
cmp al, GUI_STATE_DISABLED
|
|
je .done
|
|
|
|
xor ax, ax
|
|
|
|
; les macros peuvent modifer les registres cx, dx & bx
|
|
mov cx, [BDA_MOUSE + mouse.x]
|
|
mov dx, [BDA_MOUSE + mouse.y]
|
|
|
|
; --- Hit Test ---
|
|
cmp cx, [gs:si + widget.x]
|
|
jb .miss ; mouse.x < widget.x ?
|
|
mov bx, [gs:si + widget.x]
|
|
add bx, [gs:si + widget.w]
|
|
cmp cx, bx
|
|
ja .miss ; mouse.x >= widget.x ?
|
|
|
|
cmp dx, [gs:si + widget.y]
|
|
jb .miss ; mouse.y < widget.y ?
|
|
mov bx, [gs:si + widget.y]
|
|
add bx, [gs:si + widget.h]
|
|
cmp dx, bx
|
|
ja .miss ; mouse.y >= widget.y ?
|
|
|
|
mov bl, [BDA_MOUSE + mouse.status]
|
|
|
|
; Dispatch selon le type
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_SLIDER
|
|
jne .case_2
|
|
call gui_logic_slider
|
|
jmp .done
|
|
|
|
.case_2:
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_BUTTON
|
|
jne .case_3
|
|
call gui_logic_button
|
|
jmp .done
|
|
|
|
.case_3:
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_ROUND_BUTTON
|
|
jne .case_4
|
|
call gui_logic_button
|
|
jmp .done
|
|
|
|
.case_4:
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_CHECKBOX
|
|
jne .case_5
|
|
call gui_logic_checkbox
|
|
|
|
.case_5:
|
|
jmp .done
|
|
.miss:
|
|
; Si on n'est plus dessus, mais qu'on l'était avant (HOVER/PRESSED), il faut redessiner !
|
|
cmp byte [gs:si + widget.state], GUI_STATE_NORMAL
|
|
je .done ; Déjà normal, rien à faire
|
|
|
|
; Cas particulier : si c'est un slider en cours de drag, on continue la logique
|
|
; cmp byte [gs:si + widget.type], WIDGET_TYPE_SLIDER
|
|
; jne .reset_state
|
|
; cmp byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
; je .gui_logic_slider
|
|
|
|
.reset_state:
|
|
mov byte [gs:si + widget.state], GUI_STATE_NORMAL
|
|
xor ax, ax
|
|
|
|
.done:
|
|
pop ds
|
|
ret
|
|
|
|
; --- Logique spécifique Bouton ---
|
|
gui_logic_button:
|
|
test bl, 1 ; Clic gauche ?
|
|
jz .released
|
|
|
|
; Clic enfoncé
|
|
mov byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
xor ax, ax
|
|
ret
|
|
|
|
.released:
|
|
cmp byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
jne .hover
|
|
|
|
; Clic validé !
|
|
mov al, 1
|
|
mov byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
ret
|
|
|
|
.hover:
|
|
mov byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
xor ax, ax
|
|
ret
|
|
|
|
; --- Logique spécifique Slider ---
|
|
gui_logic_slider:
|
|
test bl, 1 ; Bouton enfoncé ?
|
|
jz .released
|
|
|
|
; Si on vient de cliquer (pas encore PRESSED), on initialise l'ancrage
|
|
cmp byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
je .do_drag
|
|
|
|
mov byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
|
|
; Calcul de l'ancrage
|
|
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
|
|
mov ax, dx
|
|
sub ax, [gs:si + widget.attr_val]
|
|
mov [gs:si + widget.attr_anchor], ax
|
|
|
|
.do_drag:
|
|
; --- LOGIQUE DE DÉPLACEMENT ---
|
|
cmp byte [gs:si + widget.attr_mode], 1
|
|
je .drag_h
|
|
cmp byte [gs:si + widget.attr_mode], 2
|
|
je .drag_v
|
|
xor ax, ax
|
|
ret
|
|
|
|
.drag_h:
|
|
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
|
|
|
|
.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]
|
|
|
|
.chk_max_v:
|
|
; Clamp Max
|
|
cmp ax, [gs:si + widget.attr_max]
|
|
jle .apply_pos
|
|
mov ax, [gs:si + widget.attr_max]
|
|
jmp .apply_pos
|
|
|
|
.apply_pos:
|
|
cmp ax, [gs:si + widget.attr_val]
|
|
je .no_change
|
|
mov [gs:si + widget.attr_val], ax
|
|
|
|
; Appel du callback on_drag
|
|
cmp word [gs:si + widget.event_drag], 0
|
|
je .force_redraw
|
|
pusha
|
|
call word [gs:si + widget.event_drag]
|
|
popa
|
|
|
|
.force_redraw:
|
|
mov byte [gs:si + widget.oldstate], 255 ; Force le redessin
|
|
|
|
.no_change:
|
|
xor ax, ax
|
|
ret
|
|
|
|
.released:
|
|
mov byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
xor ax, ax
|
|
ret
|
|
|
|
; --- Logique spécifique Checkbox ---
|
|
gui_logic_checkbox:
|
|
test bl, 1 ; Clic gauche ?
|
|
jz .released
|
|
|
|
; Clic enfoncé
|
|
mov byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
xor ax, ax
|
|
ret
|
|
|
|
.released:
|
|
cmp byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
jne .hover
|
|
|
|
; Clic validé ! Toggle value
|
|
xor word [gs:si + widget.attr_val], 1
|
|
mov byte [gs:si + widget.oldstate], 255 ; Force redraw
|
|
|
|
mov al, 1
|
|
mov byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
ret
|
|
|
|
.hover:
|
|
mov byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
xor ax, ax
|
|
ret
|
|
|
|
|
|
|
|
; Dessine le widget pointé par SI
|
|
gui_draw_single_widget:
|
|
pusha
|
|
|
|
GFX MOUSE_HIDE ; On cache la souris AVANT de commencer le dessin du widget
|
|
|
|
mov al, [gs:si + widget.state]
|
|
cmp al, [gs:si + widget.oldstate]
|
|
je .done
|
|
|
|
mov [gs:si + widget.oldstate], al ; marque comme à jour
|
|
|
|
; Chargement coords
|
|
mov ax, [gs:si + widget.x]
|
|
mov bx, [gs:si + widget.y]
|
|
|
|
mov cx, [gs:si + widget.w]
|
|
mov dx, [gs:si + widget.h]
|
|
|
|
; Calcul X2, Y2
|
|
add cx, ax
|
|
add dx, bx
|
|
|
|
mov [gs:si + widget.x2], cx
|
|
mov [gs:si + widget.y2], dx
|
|
|
|
; Dispatch selon le type
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_SLIDER
|
|
jne .case_2
|
|
call draw_slider
|
|
jmp .done
|
|
|
|
.case_2:
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_BUTTON
|
|
jne .case_3
|
|
call draw_button
|
|
jmp .done
|
|
|
|
.case_3:
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_ROUND_BUTTON
|
|
jne .case_4
|
|
call draw_round_button
|
|
jmp .done
|
|
|
|
.case_4:
|
|
cmp byte [gs:si + widget.type], WIDGET_TYPE_CHECKBOX
|
|
jne .case_5
|
|
call draw_checkbox
|
|
jmp .done
|
|
|
|
.case_5:
|
|
|
|
|
|
|
|
.done:
|
|
GFX MOUSE_SHOW ; On réaffiche la souris APRES, capturant le widget fini
|
|
popa
|
|
ret
|
|
|
|
draw_slider:
|
|
; 1. Dessiner la piste (Track)
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE_LIGHT
|
|
GFX RECTANGLE, ax, bx, cx, dx, 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
|
|
|
|
; --- Horizontal ---
|
|
mov ax, [gs:si + widget.w]
|
|
xor bh, bh
|
|
mov bl, [gs:si + widget.thumb_pct]
|
|
mul bx
|
|
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
|
|
jmp .draw_thumb
|
|
|
|
.calc_v:
|
|
; --- Vertical ---
|
|
mov ax, [gs:si + widget.h]
|
|
xor bh, bh
|
|
mov bl, [gs:si + widget.thumb_pct]
|
|
mul bx
|
|
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
|
|
|
|
.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
|
|
ret
|
|
|
|
|
|
|
|
draw_button:
|
|
; Dispatch selon état
|
|
cmp byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
je .paint_pressed
|
|
cmp byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
je .paint_hover
|
|
|
|
.draw_default_style:
|
|
; Bordure extérieure épaisse (2px)
|
|
GFX RECTANGLE, ax, bx, cx, dx, 0
|
|
inc ax
|
|
inc bx
|
|
dec cx
|
|
dec dx
|
|
GFX RECTANGLE, ax, bx, cx, dx, 0
|
|
|
|
; Bordure intérieure fine (après un gap de 1px blanc)
|
|
add ax, 2
|
|
add bx, 2
|
|
sub cx, 2
|
|
sub dx, 2
|
|
GFX RECTANGLE, ax, bx, cx, dx, 0
|
|
jmp .done
|
|
|
|
.paint_hover:
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE_LIGHT
|
|
GFX RECTANGLE, ax, bx, cx, dx, 0
|
|
jmp .done
|
|
|
|
.paint_pressed:
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_BLACK
|
|
GFX TXT_MODE, GFX_TXT_WHITE_TRANSPARENT
|
|
|
|
.done:
|
|
; on affiche le texte
|
|
; Recharger les coordonnées de base pour le texte
|
|
mov ax, [gs:si + widget.x]
|
|
mov bx, [gs:si + widget.y]
|
|
|
|
GFX TXT_MODE, GFX_TXT_BLACK_TRANSPARENT
|
|
call draw_text
|
|
ret
|
|
|
|
;
|
|
; dessine un bouton arrondi
|
|
;
|
|
draw_round_button:
|
|
; Dispatch selon état
|
|
cmp byte [gs:si + widget.state], GUI_STATE_PRESSED
|
|
je .paint_pressed
|
|
cmp byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
je .paint_hover
|
|
|
|
.paint_normal:
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE
|
|
mov di, 0 ; Couleur Noire
|
|
call draw_round_borders
|
|
GFX TXT_MODE, GFX_TXT_BLACK_TRANSPARENT
|
|
jmp .draw_text_now
|
|
|
|
.paint_hover:
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE
|
|
GFX RECTANGLE_ROUND, ax, bx, cx, dx, 0
|
|
; GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE_LIGHT
|
|
mov di, 0 ; Couleur Noire
|
|
call draw_round_borders
|
|
GFX TXT_MODE, GFX_TXT_BLACK_TRANSPARENT
|
|
jmp .draw_text_now
|
|
|
|
.paint_pressed:
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_BLACK
|
|
mov di, 1 ; Bordure Blanche sur fond noir
|
|
call draw_round_borders
|
|
GFX TXT_MODE, GFX_TXT_WHITE_TRANSPARENT
|
|
jmp .draw_text_now
|
|
|
|
.draw_text_now:
|
|
; Recharger les coordonnées de base pour le texte
|
|
mov ax, [gs:si + widget.x]
|
|
mov bx, [gs:si + widget.y]
|
|
call draw_text
|
|
jmp .done
|
|
|
|
.done:
|
|
ret
|
|
|
|
draw_round_borders:
|
|
; Bordure principale
|
|
add ax, 2
|
|
add bx, 2
|
|
sub cx, 2
|
|
sub dx, 2
|
|
GFX RECTANGLE_ROUND, ax, bx, cx, dx, di
|
|
ret
|
|
|
|
draw_checkbox:
|
|
; met un "fond" blanc
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE
|
|
|
|
cmp byte [gs:si + widget.state], GUI_STATE_HOVER
|
|
jne .no_hover
|
|
GFX RECTANGLE_ROUND, ax, bx, cx, dx, 0
|
|
.no_hover:
|
|
|
|
; Calcul coords box (centré verticalement)
|
|
mov ax, [gs:si + widget.x]
|
|
add ax, 4
|
|
mov bx, [gs:si + widget.y]
|
|
|
|
mov cx, [gs:si + widget.h]
|
|
mov dx, [gs:si + widget.w]
|
|
|
|
sub cx, GUI_CHECKBOX_SIZE
|
|
shr cx, 1
|
|
add bx, cx ; Y1
|
|
|
|
mov cx, ax
|
|
add cx, GUI_CHECKBOX_SIZE ; X2
|
|
|
|
mov dx, bx
|
|
add dx, GUI_CHECKBOX_SIZE ; Y2
|
|
|
|
; Sauvegarde coords pour le X
|
|
push ax
|
|
push bx
|
|
push cx
|
|
push dx
|
|
|
|
; Fond blanc + Bordure noire
|
|
GFX RECTANGLE_FILL, ax, bx, cx, dx, PATTERN_WHITE
|
|
GFX RECTANGLE, ax, bx, cx, dx, 0
|
|
|
|
; Check if checked
|
|
cmp word [gs:si + widget.attr_val], 0
|
|
je .draw_label
|
|
|
|
; Dessin du X (Diagonales)
|
|
add ax, 2
|
|
add bx, 2
|
|
sub cx, 2
|
|
sub dx, 2
|
|
|
|
; Optimisation : Utilisation de LINE au lieu de PUTPIXEL en boucle
|
|
; Diagonale 1 : (ax, bx) -> (cx, dx)
|
|
GFX LINE, ax, bx, cx, dx, 0
|
|
|
|
; Diagonale 2 : (ax, dx) -> (cx, bx)
|
|
GFX LINE, ax, dx, cx, bx, 0
|
|
|
|
.draw_label:
|
|
pop dx
|
|
pop cx
|
|
pop bx
|
|
pop ax
|
|
|
|
; Dessin du texte à droite (BoxX2 + 6)
|
|
add cx, 6
|
|
|
|
; Y = Centré par rapport au widget
|
|
mov bx, [gs:si + widget.y]
|
|
mov ax, [gs:si + widget.h]
|
|
sub ax, 8
|
|
shr ax, 1
|
|
add bx, ax
|
|
inc bx
|
|
|
|
GFX GOTOXY, cx, bx
|
|
mov dx, [gs:si + widget.text_seg]
|
|
mov ax, [gs:si + widget.text_ofs]
|
|
GFX WRITE, dx, ax
|
|
|
|
ret
|
|
;
|
|
; affiche le texte au centre du widget gs:si
|
|
;
|
|
draw_text:
|
|
; Petit hack pour recentrer le texte après l'effet gras
|
|
mov cx, [gs:si + widget.w]
|
|
mov dx, [gs:si + widget.h]
|
|
|
|
.text:
|
|
; --- Centrage Texte (Simplifié) ---
|
|
mov es, [gs:si + widget.text_seg]
|
|
mov di, [gs:si + widget.text_ofs]
|
|
xor cx, cx
|
|
|
|
.strlen:
|
|
cmp byte [es:di], 0
|
|
je .calc
|
|
inc cx
|
|
inc di
|
|
jmp .strlen
|
|
|
|
.calc:
|
|
shl cx, 3 ; Largeur texte px
|
|
push ax ; Sauve X original
|
|
mov ax, [gs:si + widget.w]
|
|
sub ax, cx
|
|
shr ax, 1
|
|
pop dx ; Récupère X original (poussé depuis AX)
|
|
add ax, dx ; AX = X final centré
|
|
|
|
push ax ; Sauvegarde X final pour GOTOXY
|
|
|
|
mov bx, [gs:si + widget.h]
|
|
sub bx, 8
|
|
shr bx, 1
|
|
add bx, [gs:si + widget.y] ; Y final
|
|
inc bx
|
|
|
|
pop cx ; CX = X Final, BX = Y Final
|
|
|
|
GFX GOTOXY, cx, bx
|
|
|
|
mov dx, [gs:si + widget.text_seg]
|
|
mov ax, [gs:si + widget.text_ofs]
|
|
GFX WRITE, dx, ax
|
|
ret |