From 259dc3697dae6f95aac7169d102b6b03dd8c6a12 Mon Sep 17 00:00:00 2001
From: Frater <13979047+saintfrater@users.noreply.github.com>
Date: Fri, 30 Jan 2026 21:50:08 +0100
Subject: [PATCH] widget + Event control
---
src/boot-test.asm | 209 --------------
src/boot.asm | 62 +++-
src/{ => common}/bda.asm | 51 ++--
src/common/debug_txt.asm | 1 +
src/drivers/gfx_cgam.asm | 37 ++-
src/services/gui_lib.asm | 546 ++++++++++++++++++++++++++++++++++++
src/services/gui_window.asm | 80 +++---
7 files changed, 694 insertions(+), 292 deletions(-)
delete mode 100644 src/boot-test.asm
rename src/{ => common}/bda.asm (73%)
create mode 100644 src/services/gui_lib.asm
diff --git a/src/boot-test.asm b/src/boot-test.asm
deleted file mode 100644
index e5d7859..0000000
--- a/src/boot-test.asm
+++ /dev/null
@@ -1,209 +0,0 @@
-; Compilation: nasm -f bin bios.asm -o bios.bin
-; Taille: 64 KB
-
-BITS 16
-
-section .text
-org 0x0000
-
-%define BIOS_DATA_SEG 0x0050
-
-; ------------------------------------------------------------------
-; POINT D'ENTRÉE DU BIOS (Cible du jump du reset vector)
-; ------------------------------------------------------------------
-start:
- cli
- cld
- xor ax, ax
- mov ds, ax
- mov es, ax
- mov ss, ax
- mov sp, 0x7000
-
- ; Créer une table "cohérente" de handler
- call setup_ivt
-
- ; Initialisation du PIC (Indispensable pour IRQ0/IRQ1)
- mov al, 0x11
- out 0x20, al
- out 0xA0, al
- mov al, 0x08 ; IRQ0-7 -> INT 08h-0Fh
- out 0x21, al
- mov al, 0x70 ; IRQ8-15 -> INT 70h-77h
- out 0xA1, al
-
- mov al, 0x04
- out 0x21, al
- mov al, 0x02
- out 0xA1, al
-
- mov al, 0x01
- out 0x21, al
- out 0xA1, al
-
- ; Scan et Initialisation des options ROMS (VGA BIOS, etc)
- call setup_load_rom
-
- ; initialise l'écran en mode texte
- mov ax,0x0003
- int 0x10
-
- ; Installation des ISR
- mov word [0x08*4], timer_isr
- mov word [0x08*4+2], 0xF000
-
- mov word [0x09*4], keyboard_isr
- mov word [0x09*4+2], 0xF000
-
- ; Démasquer IRQ0 et IRQ1
- mov al, 0xFC
- out 0x21, al
- sti
-
- mov ax, BIOS_DATA_SEG
- mov es, ax
- mov di, 0x0 ; Adresse physique de tes variables
- mov cx, 4
- xor ax,ax
- rep stosb ; Met 4 octets à zéro
-
-main_loop:
- mov ax,BIOS_DATA_SEG
- mov fs,ax
-
- mov al, [fs:0x00] ; Timer
- mov di, 0
- call draw_hex_byte
-
- mov al, [fs:0x01] ; Clavier
- mov di, 6
- call draw_hex_byte
-
- mov al, [fs:0x02] ; Clavier
- mov di, 12
- call draw_hex_byte
-
- jmp main_loop
-
-; ------------------------------------------------------------------
-; HANDLERS & HELPERS
-; ------------------------------------------------------------------
-default_isr:
- iret
-
-
-timer_isr:
- push ax
- push gs
-
- mov ax, BIOS_DATA_SEG
- mov gs, ax
-
- inc byte [gs:0x00]
-
- mov al, 0x20
- out 0x20, al
-
- pop gs
- pop ax
- iret
-
-keyboard_isr:
- push ax
- push gs
-
- mov ax, BIOS_DATA_SEG
- mov gs, ax
-
- in al, 0x60 ; Ack clavier
- mov byte [gs:0x02],al
-
- inc byte [gs:0x01]
-
- mov al, 0x20
- out 0x20, al
-
- pop gs
- pop ax
- iret
-
-draw_hex_byte:
- push ax
-
- mov bx, 0xb800
- mov es,bx
-
- shr al, 4
- call .draw_nibble
- pop ax
-; add di, 2
-
-.draw_nibble:
- and al, 0x0F
- add al, '0'
- cmp al, '9'
- jbe .print
- add al, 7
-.print:
- mov ah, 0x0F ; Blanc sur Noir
- stosw
- ret
-
-; ----------------------------------------------------------------------------
-; construire une table "vide" de pointeur ISR
-; ----------------------------------------------------------------------------
-setup_ivt:
- ; installation de la table d'interrupts
- xor ax, ax
- mov es, ax ; ES = 0000h -> base IVT
- xor di, di ; DI = 0000h -> offset IVT
-
- mov ax, default_isr ; offset du handler
- mov dx, cs ; segment du handler (ROM)
-
- mov cx, 256 ; 256 vecteurs
-.fill:
- stosw ; write offset (AX) -> [ES:DI], DI += 2
- xchg ax, dx ; AX = segment, DX = offset
- stosw ; write segment (AX) -> [ES:DI], DI += 2
- xchg ax, dx ; AX = offset, DX = segment
- loop .fill
- ; fin de la table d'interrupt
- ret
-
-; ---------------------------------------------------------------------------
-; Détection les ROM supplementaires
-; Teste la RAM de 0xC000 jusqu’à 0xE000, par pas de 2KB.
-;
-; Si une ROM est détectée, le code de la ROM sera appelé.
-; ---------------------------------------------------------------------------
-setup_load_rom:
- mov bx, 0xC000
-.scanloop:
- mov gs, bx
- cmp word[gs:0x0000],0xAA55 ; ROM signature
- jne .nextblock
-
- ; appel le code (en 0x0003) de la ROM
- push cs
- push word .nextblock ; address de retour
-
- push bx ; segment
- push 0x0003 ; offset entry rom
- retf ; call far ds:0x0003
-.nextblock:
- add bx, 0x80 ; add 2kb
- cmp bx, 0xE000
- jbe .scanloop
- ret
-
-; ------------------------------------------------------------------
-; Padding jusqu'au reset vector
-; ------------------------------------------------------------------
-times 0xFFF0 - ($ - $$) db 0xFF
-
-
-reset_vector:
- jmp 0xF000:start
- db '06/01/2026'
- db 0 ; le stub tient dans 16 octets (ou moins)
\ No newline at end of file
diff --git a/src/boot.asm b/src/boot.asm
index 63550c2..26170ca 100644
--- a/src/boot.asm
+++ b/src/boot.asm
@@ -26,7 +26,6 @@
; configuration du bios
;
; ---------------------------------------------------------------------------
-%define DEBUG_PORT 0xe9 ; 0x402 ou 0xe9
; initial stack
%define STACK_SEG 0x0800 ; segment de base
@@ -45,25 +44,15 @@ section .text
%include "./common/cursor.asm"
; definition du BDA
-%include "./bda.asm"
+%include "./common/bda.asm"
-; %include "./services/macros.asm"
%include "./common/debug_cga.asm"
%include "./drivers/gfx_cgam.asm"
+%include "./services/gui_lib.asm"
%include "./drivers/mouse_ps2.asm"
%include "./drivers/keyboard_ps2.asm"
%include "./services/generic.asm"
-%include "./services/gui_window.asm"
-
-err_vganok db 'VGA Not Initialized',0
-
-cpt_txt db '0123456789A123456789B123456789C123456789D123456789E123456789F123456789G123456789',0
-helloworld db 'Hello World !',0
-
-W_T db '1 - TEXT WHITE - TRANSPARENT',0
-B_T db '2 - TEXT BLACK - TRANSPARENT',0
-W_B db '3 - TEXT WHITE - ON BLACK',0
-B_W db '4 - TEXT BLACK - ON WHITE',0
+; %include "./services/gui_window.asm"
reset:
cli
@@ -111,7 +100,12 @@ reset:
push cs
pop ds
- GUI WINDOW, 50,50,250,100,helloworld
+setup_gui:
+ ; Initialiser le segment de données correct pour le texte du bouton
+ mov ax, cs ; ou DS selon où sont tes chaines
+ mov [btn_quit + widget.text_seg], ax
+
+ ; GUI WINDOW, 50,50,250,100,helloworld
; GFX RECTANGLE_DRAW, 50,50,250,100,0
@@ -126,6 +120,25 @@ reset:
mov ax,BDA_DATA_SEG
mov ds,ax
endless:
+
+ ; 1. Récupérer souris
+ mov ax, [BDA_MOUSE + mouse.x]
+ mov cx, ax ; CX = Souris X
+ mov ax, [BDA_MOUSE + mouse.y]
+ mov dx, ax ; DX = Souris Y
+ mov bl, [BDA_MOUSE + mouse.status] ; BL = Boutons
+
+ ; 2. Gérer le bouton
+ mov si, btn_quit ; SI pointe sur l'objet
+ call gui_update_button ; Met à jour l'état et redessine si besoin
+
+ ; 3. Vérifier si cliqué (AL = 1)
+ cmp al, 1
+ je action_quitter
+
+ ; 4. Afficher le curseur souris (Important : après avoir dessiné le bouton)
+ ; GFX MOUSE_MOVE
+
GFX TXT_MODE, GFX_TXT_BLACK
GFX GOTOXY, 0, 180
@@ -139,6 +152,10 @@ endless:
jmp endless
+action_quitter:
+ hlt
+ jmp action_quitter
+
; -----------------------------------------------------------
; timer ISR (IRQ -> INT)
@@ -158,6 +175,21 @@ timer_isr:
pop ax
iret
+; Déclaration d'un bouton "Quitter"
+btn_quit:
+ istruc widget
+ at widget.x, dw 10
+ at widget.y, dw 10
+ at widget.w, dw 80
+ at widget.h, dw 20
+ at widget.state, db GUI_STATE_NORMAL
+ at widget.text_ofs, dw str_quit
+ at widget.text_seg, dw 0 ; Sera rempli au runtime (CS ou DS)
+ at widget.id, db 1
+ iend
+
+str_quit db "QUITTER", 0
+
; ------------------------------------------------------------------
; Padding jusqu'au reset vector
; ------------------------------------------------------------------
diff --git a/src/bda.asm b/src/common/bda.asm
similarity index 73%
rename from src/bda.asm
rename to src/common/bda.asm
index d230208..b58a8b4 100644
--- a/src/bda.asm
+++ b/src/common/bda.asm
@@ -24,7 +24,7 @@
; https://wiki.nox-rhea.org/back2root/ibm-pc-ms-dos/hardware/informations/bios_data_area
;
-%define BDA_SEGMENT 0x0040 ; segment BDA
+%define BDA_SEGMENT 0x0040 ; segment BDA
; custom vars
%define BDA_INFO_MEM_SIZE 0x0014 ; Memory size in Kbytes
@@ -46,34 +46,33 @@
%define BKG_TOTAL_BYTES (BKG_TOTAL_DWORDS * 4) ; 64
struc mouse
- .buffer resb 4 ; i8042 input buffer
- .idx resb 1 ; index in the buffer
- .packetlen resb 1 ; max buffer len (3 ou 4)
- .status resb 1 ; mouse status (button etc)
- .wheel resb 1 ; if a packet size is 4; experimental
- .x resw 1 ;
- .y resw 1 ;
- ; cursor management
- .cur_x resw 1 ; preservation des x,y du background
- .cur_y resw 1 ;
- .cur_addr_start resw 1 ; adresse de départ de la sauvegarde
-
- .cur_drawing resb 1 ;
- .cur_counter resb 1 ; compteur de fois que le curseur a été caché (0 = visible, <0 = invisible)
- .cur_seg resw 1 ; segment / offset of the pointer
- .cur_ofs resw 1 ;
- .bkg_saved resb 1 ; background saved
- alignb 4 ; alignement 4 bytes
- .bkg_buffer resd 16 ; buffer for saved background
+ .buffer resb 4 ; i8042 input buffer
+ .idx resb 1 ; index in the buffer
+ .packetlen resb 1 ; max buffer len (3 ou 4)
+ .status resb 1 ; mouse status (button etc)
+ .wheel resb 1 ; if a packet size is 4; experimental
+ .x resw 1 ;
+ .y resw 1 ;
+ ; cursor management
+ .cur_x resw 1 ; preservation des x,y du background
+ .cur_y resw 1 ;
+ .cur_addr_start resw 1 ; adresse de départ de la sauvegarde
+ .cur_drawing resb 1 ;
+ .cur_counter resb 1 ; compteur de fois que le curseur a été caché (0 = visible, <0 = invisible)
+ .cur_seg resw 1 ; segment / offset of the pointer
+ .cur_ofs resw 1 ;
+ .bkg_saved resb 1 ; background saved
+ alignb 4 ; alignement 4 bytes
+ .bkg_buffer resd 16 ; buffer for saved background
endstruc
struc gfx
- .cur_x resw 1 ; x,y en pixel
- .cur_y resw 1
- .cur_offset resw 1 ; offset calculé pour le prochain caractère
- .cur_line_ofs resw 1 ; "interligne" +2000h ou -2000h
- .cur_shift resb 1 ; x&7 (0..7)
- .cur_mode resb 1 ;
+ .cur_x resw 1 ; x,y en pixel
+ .cur_y resw 1
+ .cur_offset resw 1 ; offset calculé pour le prochain caractère
+ .cur_line_ofs resw 1 ; "interligne" +2000h ou -2000h
+ .cur_shift resb 1 ; x&7 (0..7)
+ .cur_mode resb 1 ;
endstruc
%define BDA_MOUSE 0x0000
diff --git a/src/common/debug_txt.asm b/src/common/debug_txt.asm
index 1999bd5..c1e07e8 100644
--- a/src/common/debug_txt.asm
+++ b/src/common/debug_txt.asm
@@ -20,6 +20,7 @@
; along with this program. If not, see .
;
; =============================================================================
+%define DEBUG_PORT 0xe9 ; 0x402 ou 0xe9
debug_puts:
.next:
diff --git a/src/drivers/gfx_cgam.asm b/src/drivers/gfx_cgam.asm
index dbdd7be..dda21c7 100644
--- a/src/drivers/gfx_cgam.asm
+++ b/src/drivers/gfx_cgam.asm
@@ -215,8 +215,8 @@ cga_set_charpos:
mov bp, sp
; --- Définition des arguments ---
- %define .x word [bp+4]
- %define .y word [bp+6]
+ %define .x word [bp+4]
+ %define .y word [bp+6]
pusha
push fs
@@ -260,6 +260,10 @@ cga_set_charpos:
mov [fs:BDA_GFX + gfx.cur_offset], bx
pop fs
+
+ ; clean defs
+ %undef .x
+ %undef .y
popa
leave
ret
@@ -460,6 +464,11 @@ cga_putpixel:
and byte [es:di], ah
.done:
+
+ ; clean defs
+ %undef .x
+ %undef .y
+ %undef .color
popa
leave
ret
@@ -491,6 +500,10 @@ cga_getpixel:
pop es
pop di
+
+ ; clean defs
+ %undef .x
+ %undef .y
leave
ret
@@ -592,6 +605,12 @@ cga_line_vertical:
call cga_mouse_show
pop es
+
+ ; clean defs
+ %undef .x
+ %undef .y1
+ %undef .y2
+ %undef .color
popa
leave ; Leave gère le 'mov sp, bp / pop bp'
ret
@@ -735,6 +754,11 @@ cga_line_horizontal:
.done:
call cga_mouse_show
pop es
+
+ ; clean defs
+ %undef .x1
+ %undef .x2
+ %undef .y
popa
leave
ret
@@ -787,6 +811,10 @@ cga_draw_rect:
GFX LINE_VERT, .x1, .y1, .y2, .color
GFX LINE_VERT, .x2, .y1, .y2, .color
+ ; clean defs
+ %undef .x1
+ %undef .y1
+ %undef .x2
popa
leave
ret
@@ -938,6 +966,11 @@ cga_fill_rect:
call cga_mouse_show
pop es
+
+ ; clean defs
+ %undef .x1
+ %undef .y1
+ %undef .x2
popa
leave
ret
diff --git a/src/services/gui_lib.asm b/src/services/gui_lib.asm
new file mode 100644
index 0000000..3fd48e5
--- /dev/null
+++ b/src/services/gui_lib.asm
@@ -0,0 +1,546 @@
+; =============================================================================
+; 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 .
+;
+; =============================================================================
+
+; --- Configuration Mémoire ---
+%define GUI_RAM_SEG 0x0800 ; Segment de données UI
+%define GUI_MAX_WIDGETS 32 ; Nombre max de widgets simultanés
+
+; --- 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é
+
+; --- Structure d'un OBJET (Bouton, etc) ---
+struc widget
+ .state resb 1 ; État (0=libre, >0=utilisé)
+ .user_id resb 1 ; ID unique utilisateur
+ .x resw 1 ; Position X
+ .y resw 1 ; Position Y
+ .w resw 1 ; Largeur
+ .h resw 1 ; Hauteur
+ .state resb 1 ; État (0=Normal, 1=Hover, 2=Pressed, 3=Disabled)
+ .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"
+ 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:
+ pushad
+ push ds
+
+ mov ax, GUI_RAM_SEG
+ mov ds, ax
+
+ mov di, 0 ; On commence au début du segment 0x0800
+ mov cx, (widget_size * GUI_MAX_WIDGETS)
+ shl cx, 2 ; cx / 4
+ xor eax, eax
+ rep stosd ; Remplit tout de 0
+
+ pop ds
+ popad
+ ret
+
+; -----------------------------------------------------------------------------
+; gui_alloc_widget
+; Cherche un slot vide et retourne son adresse
+; Sortie :
+; - Succès : CF=0 (Carry Clear), SI = Offset du widget
+; - Echec : CF=1 (Carry Set), SI = Indéfini (ou 0)
+; -----------------------------------------------------------------------------
+gui_alloc_widget:
+ push cx
+ push bx
+ push ds
+
+ mov ax, GUI_RAM_SEG
+ mov ds, ax
+
+ mov si, 0 ; Début du pool
+ mov cx, GUI_MAX_WIDGETS
+
+.loop_find:
+ cmp byte [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 [si + widget.state], GUI_STATE_NORMAL ; Marquer comme occupé
+
+ ; Reset des champs critiques pour éviter les déchets
+ mov word [si + widget.callback], 0
+
+ clc ; Clear Carry Flag
+.done:
+ pop ds
+ pop bx
+ pop cx
+ ret
+
+; -----------------------------------------------------------------------------
+; gui_free_widget
+; Libère un widget
+; Entrée : SI = Pointeur widget
+; -----------------------------------------------------------------------------
+gui_free_widget:
+ mov byte [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.
+ 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
+
+ mov si, 0 ; Pointeur début tableau
+ mov di, GUI_MAX_WIDGETS ; Compteur
+
+ .loop_widgets:
+ ; Est-ce que ce slot est occupé ?
+ cmp byte [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 [si + widget.callback], 0
+ je .no_action
+
+ ; APPEL DU CALLBACK (Fonction utilisateur)
+ ; On sauve les registres car le callback peut faire n'importe quoi
+ pusha
+ call word [si + widget.callback]
+ popa
+
+ .no_action:
+ pop bx ; Restaurer boutons
+
+ ; Dessiner (si l'état a changé ou pour refresh)
+ call gui_draw_single_widget
+
+ .next_widget:
+ add si, widget_size
+ dec di
+ jnz .loop_widgets
+
+ 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 [SI].state
+gui_update_logic:
+ xor ax, ax
+
+ cmp byte [si + widget.state], GUI_STATE_DISABLED
+ je .done
+
+ ; --- Hit Test ---
+ cmp cx, [si + widget.x]
+ jl .miss
+ mov bp, [si + widget.x]
+ add bp, [si + widget.w]
+ cmp cx, bp
+ jg .miss
+
+ cmp dx, [si + widget.y]
+ jl .miss
+ mov bp, [si + widget.y]
+ add bp, [si + widget.h]
+ cmp dx, bp
+ jg .miss
+
+ ; --- Hit: Souris sur le widget ---
+ test bl, 1 ; Clic gauche ?
+ jz .released
+
+ ; Clic enfoncé
+ mov byte [si + widget.state], GUI_STATE_PRESSED
+ jmp .done
+
+.released:
+ ; Bouton relâché. Était-il pressé avant ?
+ cmp byte [si + widget.state], GUI_STATE_PRESSED
+ jne .hover
+
+ ; Clic validé !
+ mov al, 1
+ mov byte [si + widget.state], GUI_STATE_HOVER
+ jmp .done
+
+.hover:
+ mov byte [si + widget.state], GUI_STATE_HOVER
+ jmp .done
+
+.miss:
+ mov byte [si + widget.state], GUI_STATE_NORMAL
+.done:
+ ret
+
+; (Interne) Dessine le widget pointé par SI
+gui_draw_single_widget:
+ pusha
+
+ ; Chargement coords
+ mov ax, [si + widget.x]
+ mov bx, [si + widget.y]
+ mov cx, [si + widget.w]
+ mov dx, [si + widget.h]
+
+ ; Calcul X2, Y2
+ mov di, ax
+ add di, cx
+ mov bp, bx
+ add bp, dx
+
+ ; Dispatch selon état
+ mov al, [si + widget.state]
+ cmp al, GUI_STATE_PRESSED
+ je .paint_pressed
+ cmp al, GUI_STATE_HOVER
+ je .paint_hover
+
+.paint_normal:
+ GFX RECTANGLE_FILL, ax, bx, di, bp, 1 ; Blanc
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 0 ; Bord Noir
+ GFX TXT_MODE, GFX_TXT_BLACK_TRANSPARENT
+ jmp .text
+
+.paint_hover:
+ GFX RECTANGLE_FILL, ax, bx, di, bp, 1
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 0
+ ; Effet gras
+ inc ax
+ inc bx
+ dec di
+ dec bp
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 0
+
+ ; Restauration coords pour le texte
+ mov ax, [si + widget.x]
+ mov bx, [si + widget.y]
+ jmp .text_setup
+
+.paint_pressed:
+ GFX RECTANGLE_FILL, ax, bx, di, bp, 0 ; Noir
+ GFX TXT_MODE, GFX_TXT_WHITE_TRANSPARENT
+ jmp .text
+
+.text_setup:
+ ; Petit hack pour recentrer le texte après l'effet gras
+ mov cx, [si + widget.w]
+ mov dx, [si + widget.h]
+
+.text:
+ ; --- Centrage Texte (Simplifié) ---
+ push si
+ mov es, [si + widget.text_seg]
+ mov di, [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, [si + widget.w]
+ sub ax, cx
+ shr ax, 1
+ pop bx ; Récupère X original dans BX (oups, on veut ajouter)
+ add ax, bx ; X final
+
+ push ax ; X final prêt
+
+ mov bx, [si + widget.h]
+ sub bx, 8
+ shr bx, 1
+ add bx, [si + widget.y] ; Y final
+
+ pop cx ; CX = X Final, BX = Y Final
+
+ GFX GOTOXY, cx, bx
+
+ mov dx, [si + widget.text_seg]
+ mov ax, [si + widget.text_ofs]
+ GFX WRITE, dx, ax
+
+ pop si
+ popa
+ ret
+
+
+
+; =============================================================================
+; Fonction : gui_draw_button
+; Description : Dessine un bouton selon son état (.state)
+; Entrée : SI = Pointeur vers la structure widget (DS:SI)
+; =============================================================================
+gui_draw_button:
+ pusha
+
+ ; Charger les propriétés depuis la structure
+ mov ax, [si + widget.x]
+ mov bx, [si + widget.y]
+ mov cx, [si + widget.w]
+ mov dx, [si + widget.h]
+
+ ; Calculer X2 et Y2 pour le rectangle
+ mov di, ax ; X1
+ add di, cx ; X2 = X + W
+ mov bp, bx ; Y1
+ add bp, dx ; Y2 = Y + H
+
+ ; Vérifier l'état
+ cmp byte [si + widget.state], GUI_STATE_PRESSED
+ je .draw_pressed
+
+ cmp byte [si + widget.state], GUI_STATE_HOVER
+ je .draw_hover
+
+ cmp byte [si + widget.state], GUI_STATE_DISABLED
+ je .draw_disabled
+
+ .draw_normal:
+ ; Fond BLANC, Bordure NOIRE, Texte NOIR
+ ; 1. Effacer le fond (Rectangle plein blanc) -> On triche, on dessine un rect plein blanc
+ ; Note: GFX RECTANGLE_FILL utilise une couleur 0 ou 1. 1 = Blanc.
+ GFX RECTANGLE_FILL, ax, bx, di, bp, 1 ; Fond blanc
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 0 ; Bordure noire
+
+ ; Configuration texte : Noir sur Transparent
+ GFX TXT_MODE, GFX_TXT_BLACK_TRANSPARENT
+ jmp .draw_text
+
+ .draw_hover:
+ ; Comme normal, mais bordure plus épaisse ou autre effet
+ GFX RECTANGLE_FILL, ax, bx, di, bp, 1
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 0
+
+ ; Effet "Gras" sur le cadre (on dessine un 2eme rectangle à l'intérieur)
+ inc ax
+ inc bx
+ dec di
+ dec bp
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 0
+
+ ; Restaurer les coords pour le texte
+ mov ax, [si + widget.x]
+ mov bx, [si + widget.y]
+ mov di, ax
+ add di, [si + widget.w]
+
+ GFX TXT_MODE, GFX_TXT_BLACK_TRANSPARENT
+ jmp .draw_text
+
+ .draw_pressed:
+ ; Fond NOIR, Texte BLANC (Inversé)
+ GFX RECTANGLE_FILL, ax, bx, di, bp, 0 ; Fond Noir
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 1 ; Bordure Blanche (optionnel)
+
+ ; Configuration texte : Blanc sur Noir (ou transparent sur fond noir)
+ GFX TXT_MODE, GFX_TXT_WHITE_TRANSPARENT
+ jmp .draw_text
+
+ .draw_disabled:
+ ; Fond Blanc, Bordure en pointillé (difficile sans support pointillé),
+ ; on va faire simple : Bordure noire, mais on n'écrit pas le texte ou texte gris ?
+ ; Pour CGA mono, "Disabled" = Texte normal mais cadre incomplet ou hachuré.
+ ; Faisons simple : Cadre normal, mais texte non affiché ou "..."
+ GFX RECTANGLE_FILL, ax, bx, di, bp, 1
+ GFX RECTANGLE_DRAW, ax, bx, di, bp, 0
+ jmp .done ; Pas de texte pour montrer qu'il est désactivé
+
+ .draw_text:
+ ; --- Centrage du texte ---
+ ; Calcul simple : PosX = BoutonX + (LargeurBouton - (LenChaine * 8)) / 2
+ ; Calcul simple : PosY = BoutonY + (HauteurBouton - 8) / 2
+
+ push si
+ ; 1. Calculer longueur chaine
+ mov es, [si + widget.text_seg]
+ mov di, [si + widget.text_ofs]
+ xor cx, cx
+
+ .strlen:
+ cmp byte [es:di], 0
+ je .calc_pos
+ inc cx
+ inc di
+ jmp .strlen
+
+ .calc_pos:
+ ; CX = Longueur chaine
+ shl cx, 3 ; CX * 8 (largeur en pixels du texte)
+
+ mov ax, [si + widget.w]
+ sub ax, cx ; Marge horizontale totale
+ shr ax, 1 ; Diviser par 2
+ add ax, [si + widget.x] ; X final
+
+ mov bx, [si + widget.h]
+ sub bx, 8 ; Hauteur fonte (8px)
+ shr bx, 1 ; Diviser par 2
+ add bx, [si + widget.y] ; Y final
+
+ ; 2. Dessiner
+ GFX GOTOXY, ax, bx
+
+ mov dx, [si + widget.text_seg]
+ mov ax, [si + widget.text_ofs]
+ GFX WRITE, dx, ax
+
+ pop si
+
+ .done:
+ popa
+ ret
+
+; =============================================================================
+; Fonction : gui_update_button
+; Description : Met à jour l'état du bouton en fonction de la souris
+; Entrée :
+; SI = Pointeur vers widget
+; CX = Mouse X
+; DX = Mouse Y
+; BL = Mouse Status (Bit 0 = Clic gauche)
+; Sortie :
+; AL = 1 si le bouton vient d'être cliqué (Relâché), 0 sinon
+; =============================================================================
+gui_update_button:
+ push bx
+ push cx
+ push dx
+
+ xor ax, ax ; AL = Return value (0)
+
+ ; Si désactivé, on ne fait rien
+ cmp byte [si + widget.state], GUI_STATE_DISABLED
+ je .quit
+
+ ; 1. Test de collision (Hit Test)
+ ; X >= Widget.X ?
+ cmp cx, [si + widget.x]
+ jl .no_hit
+
+ ; X <= Widget.X + Widget.W ?
+ mov di, [si + widget.x]
+ add di, [si + widget.w]
+ cmp cx, di
+ jg .no_hit
+
+ ; Y >= Widget.Y ?
+ cmp dx, [si + widget.y]
+ jl .no_hit
+
+ ; Y <= Widget.Y + Widget.H ?
+ mov di, [si + widget.y]
+ add di, [si + widget.h]
+ cmp dx, di
+ jg .no_hit
+
+ ; --- HIT ! La souris est sur le bouton ---
+
+ ; Test du clic (BL bit 0)
+ test bl, 1
+ jz .hovering
+
+ ; -> Clic maintenu
+ mov byte [si + widget.state], GUI_STATE_PRESSED
+ jmp .draw_update
+
+.hovering:
+ ; Souris dessus, mais pas de clic
+ ; Si on était en PRESSED juste avant, c'est un "CLICK" valide (Relâchement)
+ cmp byte [si + widget.state], GUI_STATE_PRESSED
+ jne .just_hover
+
+ ; C'est un click valide !
+ mov al, 1 ; Return CLICKED
+ mov byte [si + widget.state], GUI_STATE_HOVER
+ jmp .draw_update
+
+.just_hover:
+ mov byte [si + widget.state], GUI_STATE_HOVER
+ jmp .draw_update
+
+.no_hit:
+ ; Souris en dehors
+ mov byte [si + widget.state], GUI_STATE_NORMAL
+
+.draw_update:
+ ; Appelle la fonction de dessin pour mettre à jour le visuel
+ call gui_draw_button
+
+.quit:
+ pop dx
+ pop cx
+ pop bx
+ ret
\ No newline at end of file
diff --git a/src/services/gui_window.asm b/src/services/gui_window.asm
index ee8b267..57b73a9 100644
--- a/src/services/gui_window.asm
+++ b/src/services/gui_window.asm
@@ -73,10 +73,10 @@ draw_window_source:
mov bp, sp
; --- Arguments ---
- %define .x word [bp+4]
- %define .y word [bp+6]
- %define .w word [bp+8]
- %define .h word [bp+10]
+ %define .xa word [bp+4]
+ %define .ya word [bp+6]
+ %define .wa word [bp+8]
+ %define .ha word [bp+10]
%define .title word [bp+12]
pusha
@@ -89,19 +89,19 @@ draw_window_source:
; ----------------------------------------------------
; On dessine un rectangle noir décalé de +1,+1
; x1+1, y1+1, x2+1, y2+1 -> NOIR
- mov ax, .x
- add ax, .w
+ mov ax, .xa
+ add ax, .wa
inc ax ; x2 + 1
mov bx, ax ; BX = right limit
- mov ax, .y
- add ax, .h
+ mov ax, .ya
+ add ax, .ha
inc ax ; y2 + 1
mov dx, ax ; DX = bottom limit
- mov ax, .x
+ mov ax, .xa
inc ax ; x1 + 1
- mov cx, .y
+ mov cx, .ya
inc cx ; y1 + 1
; Appel cga_fill_rect(x+1, y+1, x+w+1, y+h+1, BLACK)
@@ -118,17 +118,17 @@ draw_window_source:
; ----------------------------------------------------
; On dessine un rectangle BLANC par dessus l'ombre
; Cela crée le corps et "découpe" l'ombre pour ne laisser que le bord visible.
- mov bx, .x
- add bx, .w ; BX = x2
- mov dx, .y
- add dx, .h ; DX = y2
+ mov bx, .xa
+ add bx, .wa ; BX = x2
+ mov dx, .ya
+ add dx, .ha ; DX = y2
; Remplissage Blanc (Corps)
push word 1 ; Couleur 1 (Blanc)
push dx
push bx
- push word .y
- push word .x
+ push word .ya
+ push word .xa
call cga_fill_rect
add sp, 10
@@ -136,8 +136,8 @@ draw_window_source:
push word 0 ; Couleur 0 (Noir)
push dx
push bx
- push word .y
- push word .x
+ push word .ya
+ push word .xa
call cga_draw_rect
add sp, 10
@@ -148,13 +148,13 @@ draw_window_source:
%define TITLE_HEIGHT 18
; Dessiner la ligne de séparation sous le titre
- mov cx, .y
+ mov cx, .ya
add cx, TITLE_HEIGHT
push word 0 ; Couleur Noir
push cx ; Y de la ligne
push bx ; X2 (largeur fenêtre)
- push word .x ; X1
+ push word .xa ; X1
call cga_line_horizontal
add sp, 8
@@ -162,11 +162,11 @@ draw_window_source:
; On dessine une ligne noire une ligne sur deux à l'intérieur du titre
; De y+1 à y+17, step 2
- mov si, .y
+ mov si, .ya
inc si ; Commence à y+1
inc si ; Premier trait noir à y+2 (laissons 1px blanc sous le cadre haut)
- mov di, .y
+ mov di, .ya
add di, TITLE_HEIGHT
dec di ; Fin juste avant la ligne de séparation
@@ -177,7 +177,7 @@ draw_window_source:
push word 0 ; Noir
push si ; Y courant
push bx ; X2
- push word .x ; X1
+ push word .xa ; X1
call cga_line_horizontal
add sp, 8
@@ -191,9 +191,9 @@ draw_window_source:
; Petit carré blanc à gauche avec contour noir
; Position: x+6, y+4, taille 9x9 (exemple)
- mov cx, .x
+ mov cx, .xa
add cx, 6 ; Box X1
- mov dx, .y
+ mov dx, .ya
add dx, 4 ; Box Y1
mov si, cx
@@ -236,9 +236,9 @@ draw_window_source:
.calc_center:
; b) Calcul X Centré = X_win + (W_win/2) - (Len * 8 / 2)
- mov ax, .w
+ mov ax, .wa
shr ax, 1 ; W / 2
- add ax, .x ; Centre de la fenêtre
+ add ax, .xa ; Centre de la fenêtre
mov bx, cx ; Sauver Len
shl cx, 2 ; Len * 4 (car Len * 8 / 2 = Len * 4)
@@ -249,7 +249,7 @@ draw_window_source:
mov cx, ax ; Text X start
sub cx, 4 ; Padding gauche (Rect X1)
- mov dx, .y
+ mov dx, .ya
add dx, 2 ; Rect Y1 (juste sous le cadre haut)
push bx ; Sauve longueur char pour plus tard
@@ -261,7 +261,7 @@ draw_window_source:
add si, ax
add si, 4 ; Padding droite (Rect X2)
- mov di, .y
+ mov di, .ya
add di, TITLE_HEIGHT
dec di ; Rect Y2 (juste au dessus ligne séparation)
@@ -282,7 +282,7 @@ draw_window_source:
; GFX GOTOXY (X, Y) -> Y centré dans la barre de titre
; Centre barre ~9px. Font 8px. Y = Win_Y + 5
- mov dx, .y
+ mov dx, .ya
add dx, 5
; Note: GFX GOTOXY utilise des arguments pile, attention à l'ordre API
@@ -319,10 +319,10 @@ draw_window:
mov bp, sp
; --- Arguments ---
- %define .x word [bp+4]
- %define .y word [bp+6]
- %define .w word [bp+8]
- %define .h word [bp+10]
+ %define .xa word [bp+4]
+ %define .ya word [bp+6]
+ %define .wa word [bp+8]
+ %define .ha word [bp+10]
%define .title word [bp+12]
; ---- Variables Locales ---
@@ -332,19 +332,19 @@ draw_window:
; ----------------------------------------------------
; On dessine un rectangle noir décalé de +1,+1
; x1+1, y1+1, x2+1, y2+1 -> NOIR
- mov ax, .x
- add ax, .w
+ mov ax, .xa
+ add ax, .wa
inc ax ; x2 + 1
mov bx, ax ; BX = right limit
- mov ax, .y
- add ax, .h
+ mov ax, .ya
+ add ax, .ha
inc ax ; y2 + 1
mov dx, ax ; DX = bottom limit
- mov ax, .x
+ mov ax, .xa
inc ax ; x1 + 1
- mov cx, .y
+ mov cx, .ya
inc cx ; y1 + 1
GFX RECTANGLE_DRAW, ax, cx, bx, dx, 0