bios-OS/src/gui/lib.asm
2026-02-07 12:21:13 +01:00

576 lines
15 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
%define SLIDER_HORIZONTAL 1
%define SLIDER_VERTICAL 2
; --- 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 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
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]
jmp .apply_clamp
.drag_v:
; 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
; 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
; 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