diff --git a/examples/demos/stars/Makefile b/examples/demos/stars/Makefile index 1b1d068..47e0096 100644 --- a/examples/demos/stars/Makefile +++ b/examples/demos/stars/Makefile @@ -1,13 +1,15 @@ -# Makefile for building the random pixel demo -# Compiles random_pixels.has -> random_pixels.s -> random_pixels.o +# Makefile for building demos in examples/demos/stars +# Compiles each .has file to build/.s -> build/.o -> build/.exe # Links with library object files from build/ .SUFFIXES: -MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -PROJ_ROOT := $(abspath $(MAKEFILE_DIR)../../..) +PROJ_ROOT := ../../.. -HASC := cd $(PROJ_ROOT) && .venv/bin/python -m hasc.cli +# Resolve compiler runner from project root. +# Prefer `venv`, then `.venv`, then fallback to system python3. +PYTHON_BIN := $(if $(wildcard $(PROJ_ROOT)/venv/bin/python),venv/bin/python,$(if $(wildcard $(PROJ_ROOT)/.venv/bin/python),.venv/bin/python,python3)) +HASC := cd "$(PROJ_ROOT)" && "$(PYTHON_BIN)" -m hasc.cli VASM := vasmm68k_mot VLINK := vlink @@ -15,10 +17,11 @@ LIB_DIR := ../../../lib BUILD_DIR := ../../../build SRC_DIR := . -HAS_SOURCE := random_pixels.has -ASM_SOURCE := $(BUILD_DIR)/random_pixels.s -MAIN_OBJ := $(BUILD_DIR)/random_pixels.o -EXE_FILE := $(BUILD_DIR)/random_pixels.exe +HAS_SOURCES := $(wildcard $(SRC_DIR)/*.has) +DEMO_NAMES := $(basename $(notdir $(HAS_SOURCES))) +ASM_SOURCES := $(addprefix $(BUILD_DIR)/,$(addsuffix .s,$(DEMO_NAMES))) +MAIN_OBJS := $(addprefix $(BUILD_DIR)/,$(addsuffix .o,$(DEMO_NAMES))) +EXE_FILES := $(addprefix $(BUILD_DIR)/,$(addsuffix .exe,$(DEMO_NAMES))) LIB_OBJS := $(BUILD_DIR)/graphics.o \ $(BUILD_DIR)/helpers.o \ @@ -27,27 +30,31 @@ LIB_OBJS := $(BUILD_DIR)/graphics.o \ $(BUILD_DIR)/input.o \ $(BUILD_DIR)/heap.o \ $(BUILD_DIR)/bob.o \ + $(BUILD_DIR)/font8x8.o \ $(BUILD_DIR)/takeover.o VASM_FLAGS := -m68000 -Fhunk -kick1hunks -I$(LIB_DIR) VLINK_FLAGS := -bamigahunk -Bstatic -.PHONY: all clean run compile assemble link +.PHONY: all clean run compile assemble link list amiga run-vamos -all: $(EXE_FILE) +all: $(EXE_FILES) + +list: + @echo "Demos in $(SRC_DIR): $(DEMO_NAMES)" $(BUILD_DIR): @mkdir -p $(BUILD_DIR) -$(EXE_FILE): $(MAIN_OBJ) $(LIB_OBJS) - @echo "Linking demo + libraries -> $(notdir $(EXE_FILE))" +$(BUILD_DIR)/%.exe: $(BUILD_DIR)/%.o $(LIB_OBJS) + @echo "Linking demo + libraries -> $(notdir $@)" @which $(VLINK) > /dev/null || (echo "Error: $(VLINK) not found in PATH"; exit 1) $(VLINK) $(VLINK_FLAGS) $^ -o $@ -$(MAIN_OBJ): $(ASM_SOURCE) | $(BUILD_DIR) +$(BUILD_DIR)/%.o: $(BUILD_DIR)/%.s | $(BUILD_DIR) @echo "Assembling $(notdir $<) -> $(notdir $@)" @which $(VASM) > /dev/null || (echo "Error: $(VASM) not found in PATH"; exit 1) - $(VASM) $(VASM_FLAGS) -L $(BUILD_DIR)/random_pixels.lst -o $@ $< + $(VASM) $(VASM_FLAGS) -L $(BUILD_DIR)/$*.lst -o $@ $< $(BUILD_DIR)/graphics.o: $(LIB_DIR)/graphics.s | $(BUILD_DIR) @echo "Assembling graphics.s -> $(notdir $@)" @@ -81,17 +88,39 @@ $(BUILD_DIR)/bob.o: $(LIB_DIR)/bob.s | $(BUILD_DIR) @echo "Assembling bob.s -> $(notdir $@)" $(VASM) $(VASM_FLAGS) -L $(BUILD_DIR)/bob.lst -o $@ $< -$(ASM_SOURCE): $(HAS_SOURCE) | $(BUILD_DIR) - @echo "Compiling $(HAS_SOURCE) -> $(notdir $@)" +$(BUILD_DIR)/font8x8.o: $(LIB_DIR)/font8x8.s | $(BUILD_DIR) + @echo "Assembling font8x8.s -> $(notdir $@)" + $(VASM) $(VASM_FLAGS) -L $(BUILD_DIR)/font8x8.lst -o $@ $< + + +$(BUILD_DIR)/%.s: %.has | $(BUILD_DIR) + @echo "Compiling $< -> $(notdir $@)" @mkdir -p $(dir $@) - $(HASC) examples/demos/stars/$< -o build/random_pixels.s + $(HASC) examples/demos/stars/$< -o build/$*.s -compile: $(ASM_SOURCE) -assemble: $(MAIN_OBJ) -link: $(EXE_FILE) +compile: $(ASM_SOURCES) +assemble: $(MAIN_OBJS) +link: $(EXE_FILES) clean: - rm -rf $(BUILD_DIR)/random_pixels.o $(BUILD_DIR)/random_pixels.s $(BUILD_DIR)/random_pixels.exe $(BUILD_DIR)/random_pixels.lst - -run: $(EXE_FILE) - ./$(EXE_FILE) + rm -rf $(ASM_SOURCES) $(MAIN_OBJS) $(EXE_FILES) $(addprefix $(BUILD_DIR)/,$(addsuffix .lst,$(DEMO_NAMES))) + +DEMO ?= random_pixels +TARGET_EXE := $(BUILD_DIR)/$(DEMO).exe + +# Build one selected Amiga executable (e.g. `make amiga DEMO=Texts`). +amiga: $(TARGET_EXE) + @echo "Amiga executable ready: $(TARGET_EXE)" + +# Run the Amiga executable via vamos when available. +run-vamos: $(TARGET_EXE) + @if which vamos > /dev/null; then \ + echo "Running $(TARGET_EXE) with vamos"; \ + vamos $(TARGET_EXE); \ + else \ + echo "vamos not found. Built Amiga executable: $(TARGET_EXE)"; \ + echo "Run it on Amiga/emulator manually."; \ + fi + +# Keep `run` as alias for Amiga-oriented execution. +run: run-vamos diff --git a/examples/demos/stars/Texts.has b/examples/demos/stars/Texts.has new file mode 100644 index 0000000..ebfc150 --- /dev/null +++ b/examples/demos/stars/Texts.has @@ -0,0 +1,126 @@ +#pragma lockreg(a5); + +// Visual regression demo for Text/Print in 320x256x32 mode. +// Stresses cursor handling, newline behavior, wrapping, colors, and font attachment. + +extern var fonts: int; +const KEY_ESC = 69; + +code main: + extern func SetGraphicsMode(mode: int) -> int; + extern func ClearScreen() -> int; + extern func SetColor(id: int, col: int) -> int; + extern func SetFont(ptr: int) -> int; + extern func Text(x: int, y: int, msg: int, color: int) -> int; + extern func Print(msg: int, color: int) -> int; + extern func UpdateCopperList() -> int; + extern func WaitVBlank() -> int; + extern func InitKeyboard() -> void; + extern func GetKey() -> byte; + extern func TakeSystem() -> void; + extern func ReleaseSystem() -> void; + + public main; + + asm { + jsr TakeSystem + jsr main + jmp ReleaseSystem + } + + proc main() -> int { + var running: int = 1; + var key: byte; + var frame: int = 0; + var blink: int = 0; + + if (SetGraphicsMode(0) == -1) { + return -1; + } + + call InitKeyboard(); + call SetFont(&fonts); + call ClearScreen(); + + // Keep palette explicit so color bugs are easy to spot. + call SetColor(0, $000); + call SetColor(1, $FFF); + call SetColor(2, $F80); + call SetColor(3, $0F0); + call SetColor(4, $08F); + call SetColor(5, $FF0); + call SetColor(6, $F0F); + call SetColor(7, $0FF); + + call Text(1, 1, &title_txt, 2); + call Text(1, 2, &info_txt, 3); + call Text(1, 4, &phase1_hdr, 5); + call Text(1, 5, &phase1_hint, 1); + + // Phase 1: deterministic Print-only block. + call Print(&baseline_block, 2); + + call Text(1, 12, &phase2_hdr, 6); + call Text(1, 13, &phase2_hint, 1); + + // Phase 2: mixed Print/Text interactions. + call Print(&stress_seq_a, 4); + call Print(&stress_seq_lf, 6); + call Print(&stress_seq_cr, 7); + call Text(2, 17, &text_anchor_a, 5); + call Text(2, 19, &text_anchor_b, 7); + call Print(&post_text_seq, 2); + call Print(&wrap_test_seq, 3); + + call Text(1, 30, &exit_hint_txt, 1); + call UpdateCopperList(); + + while (running == 1) { + call WaitVBlank(); + + // Small periodic update to keep exercising Text on same line. + frame = frame + 1; + if (frame == 25) { + frame = 0; + if (blink == 0) { + blink = 1; + call Text(1, 28, &pulse_a_txt, 4); + } else { + blink = 0; + call Text(1, 28, &pulse_b_txt, 6); + } + call UpdateCopperList(); + } + + key = GetKey(); + if (key == KEY_ESC) { + running = 0; + } + } + + return 0; + } + +data text_data: + title_txt.b = "Texts.has - Print/Text Regression Demo", 0 + info_txt.b = "Mode: 320x256x32 | font: lib/font8x8.s", 0 + + phase1_hdr.b = "PHASE 1: BASELINE (PRINT ONLY)", 0 + phase1_hint.b = "Expected: full labels, stable left edge, no missing first chars.", 0 + baseline_block.b = 10, "BASE-A: 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ", 10, "BASE-B: AAAA BBBB CCCC DDDD", 10, "BASE-C: left edge marker -> [#]", 0 + + phase2_hdr.b = "PHASE 2: STRESS (PRINT + TEXT MIX)", 0 + phase2_hint.b = "Expected: Text() anchors do not permanently move Print cursor.", 0 + stress_seq_a.b = 10, "STRESS-A: mixed cursor test start", 0 + stress_seq_lf.b = 10, "STRESS-B after LF should start on next line.", 0 + stress_seq_cr.b = 13, "STRESS-C after CR should also start on next line.", 0 + + text_anchor_a.b = "TEXT anchor A at (2,17)", 0 + text_anchor_b.b = "TEXT anchor B at (2,19)", 0 + post_text_seq.b = "STRESS-D cursor should continue from STRESS-C, not Text anchors.", 0 + + wrap_test_seq.b = "WRAP: 0123456789012345678901234567890123456789 <- end", 0 + + pulse_a_txt.b = "Pulse line via Text(): state A", 0 + pulse_b_txt.b = "Pulse line via Text(): state B", 0 + exit_hint_txt.b = "Press ESC to exit demo.", 0 diff --git a/examples/msgbox_demo.has b/examples/msgbox_demo.has new file mode 100644 index 0000000..66a23eb --- /dev/null +++ b/examples/msgbox_demo.has @@ -0,0 +1,162 @@ +// msgbox_demo.has +// Demonstrates the GUI widget library: DrawMsgBox with word-wrapped text. +// +// Graphics mode: 320x256x32 (mode 0, 5 bitplanes, line-interleaved) +// Font: lib/font8x8.s (8x8 pixel characters) +// +// Palette indices used: +// 0 = black ($000) - screen background +// 1 = white ($FFF) - border + text +// 4 = blue ($00F) - window background +// 5 = yellow ($FF0) - second window border +// 8 = mid-grey ($888) - second window background +// +// Build (link gui.s, graphics.s, font8x8.s alongside the generated .s): +// python -m hasc.cli examples/msgbox_demo.has -o build/msgbox_demo.s +// vasmm68k_mot -Fhunkexe -I lib/ -o build/msgbox_demo.o build/msgbox_demo.s lib/gui.s lib/graphics.s lib/font8x8.s lib/helpers.s +// vlink -bamigahunk build/msgbox_demo.o -o build/msgbox_demo.exe + +#pragma lockreg(a5); + +extern var fonts: int; // font bitmap data from lib/font8x8.s + +code main: + extern func SetGraphicsMode(mode: int) -> int; + extern func ClearScreen() -> int; + extern func SetFont(ptr: int) -> int; + extern func SetColor(id: int, col: int) -> int; + extern func UpdateCopperList() -> int; + extern func WaitVBlank() -> int; + extern func TakeSystem() -> void; + extern func ReleaseSystem() -> void; + + // GUI library functions from lib/gui.s + extern func DrawBox(x:int, y:int, w:int, h:int, bg:int, border:int) -> int; + extern func DrawMsgBox(x:int, y:int, w:int, h:int, bg:int, border:int, str:int, tc:int) -> int; + extern func DrawButton(x:int, y:int, w:int, h:int, bg:int, border:int, str:int, tc:int) -> int; + // Mouse event manager (lib/input.s + lib/gui.s) + extern func ReadMouse() -> void; + extern func GuiPollMouse() -> void; + extern func GuiHitTestRect(x:int, y:int, w:int, h:int) -> int; + extern func GetGuiMouseX() -> int; + extern func GetGuiMouseY() -> int; + // Hardware sprite cursor (lib/sprite.s) + extern func CreateSprite(idx:int, ptr:int) -> int; + extern func ApplySpritePalette(idx:int) -> int; + extern func ShowSprite(idx:int) -> int; + extern func SetSpritePosition(idx:int, x:int, y:int) -> int; + + public main; + + asm { + jsr TakeSystem + jsr main + jmp ReleaseSystem + } + + proc main() -> int { + var result: int; + var frame: int = 0; + var mx: int = 0; + var my: int = 0; + + // Initialise 320x256x32 mode + result = SetGraphicsMode(0); + if (result == -1) { + return -1; + } + + // Attach the 8x8 bitmap font + call SetFont(&fonts); + + // Customise a few palette entries for the demo + call SetColor(0, $000); // 0 = black (background) + call SetColor(1, $FFF); // 1 = white + call SetColor(4, $00F); // 4 = blue (window fill) + call SetColor(5, $FF0); // 5 = yellow + call SetColor(8, $444); // 8 = dark grey + + call ClearScreen(); + + // --------------------------------------------------------- + // Sprite cursor: hardware sprite 0 used as mouse pointer + // cursor_pal (4 words immediately before cursor) sets color1=$FFF (white) + // --------------------------------------------------------- + result = CreateSprite(0, &cursor); // copy fast->chip RAM; store palette + call ApplySpritePalette(0); // write palette into copper list (COLOR17-19) + call ShowSprite(0); // mark slot visible + call SetSpritePosition(0, 160, 128); // initial position: screen centre + + // --------------------------------------------------------- + // Window 1: short message - fits on one line + // Position (16, 16), size 200x24 pixels + // Background: blue (4), border: white (1), text: white (1) + // --------------------------------------------------------- + result = DrawMsgBox(16, 16, 200, 24, 4, 1, &msg_short, 1); + + // --------------------------------------------------------- + // Window 2: long message - wraps across multiple lines + // Position (16, 56), size 288x72 pixels (36 cols x 9 rows) + // Background: dark grey (8), border: yellow (5), text: white (1) + // --------------------------------------------------------- + result = DrawMsgBox(16, 56, 288, 72, 8, 5, &msg_long, 1); + + // --------------------------------------------------------- + // Window 3: minimal box with no text - just a filled frame + // Position (16, 144), size 64x32 + // --------------------------------------------------------- + result = DrawBox(16, 144, 64, 32, 4, 1); + + // --------------------------------------------------------- + // Exit button: + // Move the mouse over it and click the left button to exit. + // Position (120, 140), size 80x16 + // --------------------------------------------------------- + result = DrawButton(120, 140, 80, 16, 8, 1, &btn_label, 1); + + // --------------------------------------------------------- + // Window 4: stress test - word longer than max_cols (hard break) + // Position (16, 192), size 96x48 pixels (12 cols x 6 rows) + // --------------------------------------------------------- + result = DrawMsgBox(16, 192, 96, 48, 4, 5, &msg_hardwrap, 1); + + // Show the frame + call UpdateCopperList(); + + // Event loop: hold display until the Exit button is clicked + do { + call WaitVBlank(); + call ReadMouse(); // update raw mouse deltas + button state + call GuiPollMouse(); // accumulate abs pixel position; detect click edge + // Move hardware sprite to follow the mouse cursor + mx = GetGuiMouseX(); + my = GetGuiMouseY(); + call SetSpritePosition(0, mx, my); + if (GuiHitTestRect(100, 132, 120, 24) == 1) { + frame = 1; // Exit button clicked: leave the loop + } + call UpdateCopperList(); + } while (frame == 0); + + return 0; + } + +// String data for the demo windows +data demo_strings: + btn_label.b = "Exit", 0 + + msg_short.b = "Hello from the HAS GUI library!", 0 + + msg_long.b = "This is a longer message that will not fit on a single line. The word-wrap engine breaks at spaces so no word is split. Rendering stops when the window is full.", 0 + + msg_hardwrap.b = "ABCDEFGHIJKLMNOPQRSTUVWXYZ no-space-here forced-break test end.", 0 + +// Sprite 0 cursor data (classic 11-line arrow, top-left hotspot) +// Palette word layout (4 words = 8 bytes before cursor label, required by CreateSprite): +// color0 = $000 (transparent), color1 = $FFF (white, used for all pixels), +// color2 = $CCC (light grey), color3 = $888 (mid grey) +// Sprite data layout: height, ctrl0, ctrl1, (plane0,plane1) x height, 0,0 terminator +// All rows use plane0=shape, plane1=$0000 so pixels select color1 (white). +data cursor_data: + cursor_pal.w = $000, $FFF, $CCC, $888 + cursor.w = 11, 0, 0, $8000, 0, $C000, 0, $E000, 0, $F000, 0, $F800, 0, $FC00, 0, $FE00, 0, $EC00, 0, $C600, 0, $8300, 0, $0300, 0, 0, 0 diff --git a/lib/graphics.s b/lib/graphics.s index 0f35cf9..8854a32 100644 --- a/lib/graphics.s +++ b/lib/graphics.s @@ -37,6 +37,9 @@ GFX_FONT_PLANES EQU 5 ; Font assets are always expanded to 5 XDEF SetColor XDEF LoadPalette XDEF ShowPicture + XDEF _DrawChar + XDEF gfx_text_cursor_x + XDEF gfx_text_cursor_y ; ---------- Graphics support wrappers ---------- ; These minimal wrappers map the public names used by generated code diff --git a/lib/gui.i b/lib/gui.i new file mode 100644 index 0000000..c2f7709 --- /dev/null +++ b/lib/gui.i @@ -0,0 +1,70 @@ +; gui.i - Include file for gui.s +; +; Provides: +; - GADGET struct field offsets (EQU constants) +; - GADGET_TYPE constants +; - XREF declarations for all public gui.s entry points +; +; Usage in assembly: +; include "gui.i" +; +; Usage in HAS (declare each needed function with extern func): +; extern func FillRect(x:int, y:int, w:int, h:int, color:int) -> int; +; ... (see bottom of this file for the full set) + + ifnd GUI_I +GUI_I = 1 + +; ============================================================ +; GADGET struct layout (20 bytes) +; Base address passed to DrawGadget(gadget_ptr). +; All coordinate / size fields are WORD (sign-extended to LONG by callee). +; ============================================================ + +GADGET_X EQU 0 ; word - screen X position in pixels +GADGET_Y EQU 2 ; word - screen Y position in pixels +GADGET_W EQU 4 ; word - width in pixels (multiples of 8 recommended) +GADGET_H EQU 6 ; word - height in pixels (multiples of 8 recommended) +GADGET_BG EQU 8 ; word - background fill palette index (0-31 for mode 0) +GADGET_BORDER EQU 10 ; word - border frame palette index (0-31) +GADGET_TEXT EQU 12 ; long - pointer to null-terminated message string +GADGET_TCOLOR EQU 16 ; word - text palette index (0-31) +GADGET_TYPE EQU 18 ; word - gadget type selector (see GADGET_TYPE_* below) +GADGET_SIZE EQU 20 ; struct size in bytes (for allocation) + +; ============================================================ +; GADGET_TYPE values +; ============================================================ + +GADGET_TYPE_MSGBOX EQU 0 ; message box with word-wrapped text +GADGET_TYPE_BUTTON EQU 1 ; clickable button with centred label + +; ============================================================ +; External references (assembled in gui.s) +; ============================================================ + + XREF FillRect + XREF DrawHLine + XREF DrawVLine + XREF DrawBox + XREF DrawWrappedText + XREF DrawMsgBox + XREF DrawButton + XREF DrawGadget + XREF GuiPollMouse + XREF GuiHitTest + XREF GuiHitTestRect + + endif ; GUI_I + +; ============================================================ +; HAS extern func declarations (copy into your .has file as needed) +; ============================================================ +; +; extern func FillRect(x:int, y:int, w:int, h:int, color:int) -> int; +; extern func DrawHLine(x:int, y:int, len:int, color:int) -> int; +; extern func DrawVLine(x:int, y:int, len:int, color:int) -> int; +; extern func DrawBox(x:int, y:int, w:int, h:int, bg:int, border:int) -> int; +; extern func DrawWrappedText(cx:int, cy:int, cols:int, rows:int, str:int, color:int) -> int; +; extern func DrawMsgBox(x:int, y:int, w:int, h:int, bg:int, border:int, str:int, tc:int) -> int; +; extern func DrawGadget(gadget_ptr:int) -> int; diff --git a/lib/gui.s b/lib/gui.s new file mode 100644 index 0000000..8ace336 --- /dev/null +++ b/lib/gui.s @@ -0,0 +1,1004 @@ +; gui.s - HAS GUI widget library for Motorola 68000 / Amiga +; +; Supports mode 0 (320x256x32, 5 planes, 40 bytes/row) and +; mode 1 (640x256x16, 4 planes, 80 bytes/row); both line-interleaved. +; Screen memory layout: +; mode 0 byte offset for pixel (x,y): y*200 + plane*40 + (x>>3) +; mode 1 byte offset for pixel (x,y): y*320 + plane*80 + (x>>3) +; bit within byte: (7 - (x & 7)) [bit 7 = leftmost pixel] +; +; Calling convention (matches graphics.s): +; link a6,#N with arguments at 8(a6), 12(a6), 16(a6), ... (longs) +; Return value in d0 (0 = success, -1 = error). +; Callee saves d1-d7 / a0-a4; d0 is the return register. +; a5 is intentionally not saved (used as custom chip base via #pragma lockreg(a5)). +; Arguments pushed right-to-left (first arg is at lowest address = 8(a6)). +; +; External dependencies (from graphics.s): +; gfx_current_screen_ptr -- long pointer to active screen buffer +; gfx_current_mode -- word: 0=lores, 1=hires, 2=HAM6 +; _DrawChar -- draw single char: D0=ASCII, D1=color +; gfx_text_cursor_x -- word: current text column +; gfx_text_cursor_y -- word: current text row +; +; Public entry points (all use stack-frame calling convention): +; FillRect(x,y,w,h,color) +; DrawHLine(x,y,len,color) +; DrawVLine(x,y,len,color) +; DrawBox(x,y,w,h,bg_color,border_color) +; DrawWrappedText(cx,cy,max_cols,max_rows,str_ptr,text_color) +; DrawMsgBox(x,y,w,h,bg_color,border_color,str_ptr,text_color) +; DrawGadget(gadget_ptr) + + + include "hardware.i" + + SECTION gui_code,CODE + + XDEF FillRect + XDEF DrawHLine + XDEF DrawVLine + XDEF DrawBox + XDEF DrawWrappedText + XDEF DrawMsgBox + XDEF DrawButton + XDEF DrawGadget + XDEF GuiPollMouse + XDEF GuiHitTest + XDEF GuiHitTestRect + XDEF GetGuiMouseX + XDEF GetGuiMouseY + + XREF gfx_current_screen_ptr + XREF gfx_current_mode + XREF _DrawChar + XREF gfx_text_cursor_x + XREF gfx_text_cursor_y + XREF GetMouseDX + XREF GetMouseDY + XREF GetMouseLBtn + + +; ============================================================ +; FillRect(x, y, w, h, color) +; 8(a6) = x - left pixel (long) +; 12(a6) = y - top pixel (long) +; 16(a6) = w - width in pixels (long) +; 20(a6) = h - height in pixels (long) +; 24(a6) = color - palette index 0-31 (long) +; +; Fills a solid axis-aligned rectangle using the current mode's line-interleaved layout. +; Reads gfx_current_mode at call time: mode 0 uses 5 planes/40 bytes, mode 1 uses 4 planes/80 bytes. +; For each bitplane: if (color >> plane) & 1, SET covered bits; otherwise CLEAR them. +; Handles partial left/right bytes via bit-masks. +; Returns d0 = 0. +; ============================================================ +FillRect: + link a6,#-8 ; locals: -1(a6)=lmask, -2(a6)=rmask, -4(a6)=width_bytes, -6(a6)=row_stride, -8(a6)=num_planes + movem.l d1-d7/a0-a4,-(sp) + + ; Bail on zero/negative size + move.l 16(a6),d0 ; w + tst.l d0 + ble .fr_exit + move.l 20(a6),d0 ; h + tst.l d0 + ble .fr_exit + + ; ---- Determine mode-dependent parameters ---- + move.w gfx_current_mode,d0 + tst.w d0 + bne .fr_hires + ; Mode 0: 320x256, 5 planes, 40 bytes/row + move.w #40,-4(a6) ; width_bytes + move.w #200,-6(a6) ; row_stride = 5*40 + move.w #5,-8(a6) ; num_planes + bra .fr_mode_set +.fr_hires: + ; Mode 1: 640x256, 4 planes, 80 bytes/row + move.w #80,-4(a6) ; width_bytes + move.w #320,-6(a6) ; row_stride = 4*80 + move.w #4,-8(a6) ; num_planes +.fr_mode_set: + + ; ---- Precompute left_byte and right_byte (in address registers a2/a3) ---- + ; left_byte = x >> 3 + move.l 8(a6),d0 + lsr.l #3,d0 + move.l d0,a2 ; a2 = left_byte + + ; right_byte = (x + w - 1) >> 3 + move.l 8(a6),d0 + add.l 16(a6),d0 + subq.l #1,d0 ; x1 = x + w - 1 + lsr.l #3,d0 + move.l d0,a3 ; a3 = right_byte + + ; ---- Precompute lmask = gui_lmask[ x & 7 ] ---- + move.l 8(a6),d0 + and.l #7,d0 + lea gui_lmask,a1 + move.b (a1,d0.w),-1(a6) ; store lmask byte + + ; ---- Precompute rmask = gui_rmask[ (x+w-1) & 7 ] ---- + move.l 8(a6),d0 + add.l 16(a6),d0 + subq.l #1,d0 + and.l #7,d0 + lea gui_rmask,a1 + move.b (a1,d0.w),-2(a6) ; store rmask byte + + move.l gfx_current_screen_ptr,a0 ; a0 = screen base (stays constant) + move.l 24(a6),d4 ; d4 = color (constant) + + ; ---- Outer loop: plane 0..4 ---- + moveq #0,d5 ; d5 = plane counter + +.fr_plane_loop: + ; fill_byte: if (color >> plane) & 1 then 0xFF else 0x00 + move.l d4,d6 + lsr.l d5,d6 ; shift color right by plane count + and.l #1,d6 ; isolate LSB + neg.l d6 ; 1 -> 0xFFFFFFFF, 0 -> 0x00000000 + + ; Reset row cursor to y for this plane + move.l 12(a6),d7 ; d7 = current absolute pixel row (= y) + move.l 20(a6),d3 + subq.l #1,d3 ; d3 = h-1 (dbra counter) + +.fr_row_loop: + ; Compute: a1 = screen_base + row*200 + plane*40 + left_byte + move.l d7,d0 + mulu.w -6(a6),d0 ; d0 = row * row_stride + move.l d5,d1 + mulu.w -4(a6),d1 ; d1 = plane * width_bytes + add.l d1,d0 + add.l a2,d0 ; d0 += left_byte + move.l a0,a1 + add.l d0,a1 ; a1 = address of first byte for this row/plane + + ; ---- Single-byte span? ---- + cmpa.l a3,a2 ; left_byte == right_byte? + bne .fr_multi_bytes + + ; Combined mask = lmask & rmask + moveq #0,d0 + move.b -1(a6),d0 ; d0 = lmask + moveq #0,d1 + move.b -2(a6),d1 ; d1 = rmask + and.b d1,d0 ; d0 = combined mask + tst.b d6 ; fill_byte == 0? + beq .fr_single_clear + ; SET bits under mask + move.b (a1),d1 + or.b d0,d1 + move.b d1,(a1) + bra .fr_row_next + +.fr_single_clear: + ; CLEAR bits under mask + not.b d0 ; ~combined_mask + move.b (a1),d1 + and.b d0,d1 + move.b d1,(a1) + bra .fr_row_next + + ; ---- Multi-byte span ---- +.fr_multi_bytes: + ; Left partial byte: apply lmask + moveq #0,d0 + move.b -1(a6),d0 ; d0 = lmask + tst.b d6 + beq .fr_left_clear + move.b (a1),d1 + or.b d0,d1 + move.b d1,(a1)+ ; write and advance + bra .fr_mid_bytes + +.fr_left_clear: + not.b d0 ; ~lmask + move.b (a1),d1 + and.b d0,d1 + move.b d1,(a1)+ ; write and advance + +.fr_mid_bytes: + ; Middle full bytes: count = right_byte - left_byte - 1 + move.l a3,d2 + sub.l a2,d2 ; d2 = right_byte - left_byte + subq.l #1,d2 ; d2 = middle byte count (may be 0) + tst.l d2 + beq .fr_right_byte ; 0 middle bytes: go straight to right (invariant: d2>=0) + subq.l #1,d2 ; d2 = dbra count (count-1) + tst.b d6 + beq .fr_mid_clear + +.fr_mid_set: + move.b #$FF,(a1)+ + dbra d2,.fr_mid_set + bra .fr_right_byte + +.fr_mid_clear: + clr.b (a1)+ + dbra d2,.fr_mid_clear + ; fall through to right byte + +.fr_right_byte: + ; Right partial byte: apply rmask + moveq #0,d0 + move.b -2(a6),d0 ; d0 = rmask + tst.b d6 + beq .fr_right_clear + move.b (a1),d1 + or.b d0,d1 + move.b d1,(a1) + bra .fr_row_next + +.fr_right_clear: + not.b d0 ; ~rmask + move.b (a1),d1 + and.b d0,d1 + move.b d1,(a1) + +.fr_row_next: + addq.l #1,d7 ; advance to next scanline + dbra d3,.fr_row_loop ; h iterations + + addq.l #1,d5 ; next plane + cmp.w -8(a6),d5 + blt .fr_plane_loop ; num_planes iterations + +.fr_exit: + moveq #0,d0 + movem.l (sp)+,d1-d7/a0-a4 + unlk a6 + rts + + +; ============================================================ +; DrawHLine(x, y, len, color) +; Draw a horizontal line of 'len' pixels starting at (x,y). +; Delegates to FillRect(x, y, len, 1, color). +; ============================================================ +DrawHLine: + link a6,#0 + move.l 20(a6),-(sp) ; color (arg5) + move.l #1,-(sp) ; h=1 (arg4) + move.l 16(a6),-(sp) ; len=w (arg3) + move.l 12(a6),-(sp) ; y (arg2) + move.l 8(a6),-(sp) ; x (arg1) + jsr FillRect + lea 20(sp),sp + ; d0 = 0 from FillRect + unlk a6 + rts + + +; ============================================================ +; DrawVLine(x, y, len, color) +; Draw a vertical line of 'len' pixels starting at (x,y). +; Delegates to FillRect(x, y, 1, len, color). +; ============================================================ +DrawVLine: + link a6,#0 + move.l 20(a6),-(sp) ; color (arg5) + move.l 16(a6),-(sp) ; len=h (arg4) + move.l #1,-(sp) ; w=1 (arg3) + move.l 12(a6),-(sp) ; y (arg2) + move.l 8(a6),-(sp) ; x (arg1) + jsr FillRect + lea 20(sp),sp + unlk a6 + rts + + +; ============================================================ +; DrawBox(x, y, w, h, bg_color, border_color) +; 8(a6) = x +; 12(a6) = y +; 16(a6) = w +; 20(a6) = h +; 24(a6) = bg_color - fill color for interior +; 28(a6) = border_color - color for 1-pixel border frame +; +; Draws a filled rectangle then a 1-pixel border on all four edges. +; Interior (bg) fill is drawn first; border is painted on top. +; Left/right edge height = h-2 (spans interior rows only, avoiding +; overlap with top/bottom corners which are already filled). +; ============================================================ +DrawBox: + link a6,#0 + movem.l d1-d5,-(sp) ; FillRect preserves d1-d7, so these survive calls + + move.l 8(a6),d1 ; d1 = x (preserved across FillRect calls) + move.l 12(a6),d2 ; d2 = y + move.l 16(a6),d3 ; d3 = w + move.l 20(a6),d4 ; d4 = h + + tst.l d3 + ble .db_exit + tst.l d4 + ble .db_exit + + ; ---- 1. Background fill: FillRect(x, y, w, h, bg) ---- + move.l 24(a6),-(sp) + move.l d4,-(sp) + move.l d3,-(sp) + move.l d2,-(sp) + move.l d1,-(sp) + jsr FillRect + lea 20(sp),sp + ; d1-d4 preserved by FillRect + + ; ---- 2. Top edge: FillRect(x, y, w, 1, border) ---- + move.l 28(a6),-(sp) + move.l #1,-(sp) + move.l d3,-(sp) + move.l d2,-(sp) + move.l d1,-(sp) + jsr FillRect + lea 20(sp),sp + + ; ---- 3. Bottom edge: FillRect(x, y+h-1, w, 1, border) ---- + move.l d4,d5 + add.l d2,d5 + subq.l #1,d5 ; d5 = y + h - 1 + move.l 28(a6),-(sp) + move.l #1,-(sp) + move.l d3,-(sp) + move.l d5,-(sp) ; y+h-1 + move.l d1,-(sp) + jsr FillRect + lea 20(sp),sp + + ; ---- 4 & 5: Left/right edges (only when h > 2) ---- + move.l d4,d5 + subq.l #2,d5 ; d5 = h - 2 (interior edge height) + tst.l d5 + ble .db_exit + + ; 4. Left edge: FillRect(x, y+1, 1, h-2, border) + move.l 28(a6),-(sp) + move.l d5,-(sp) ; h-2 + move.l #1,-(sp) ; w=1 + move.l 12(a6),d0 + addq.l #1,d0 ; d0 = y+1 + move.l d0,-(sp) + move.l d1,-(sp) ; x + jsr FillRect + lea 20(sp),sp + ; d5 = h-2 preserved (FillRect preserves d5) + + ; 5. Right edge: FillRect(x+w-1, y+1, 1, h-2, border) + move.l 28(a6),-(sp) + move.l d5,-(sp) ; h-2 + move.l #1,-(sp) ; w=1 + move.l 12(a6),d0 + addq.l #1,d0 ; y+1 + move.l d0,-(sp) + move.l d1,d0 + add.l d3,d0 + subq.l #1,d0 ; x+w-1 + move.l d0,-(sp) + jsr FillRect + lea 20(sp),sp + +.db_exit: + moveq #0,d0 + movem.l (sp)+,d1-d5 + unlk a6 + rts + + +; ============================================================ +; DrawWrappedText(cx, cy, max_cols, max_rows, str_ptr, text_color) +; 8(a6) = cx - starting character column (0-based, char units) +; 12(a6) = cy - starting character row (0-based, char units) +; 16(a6) = max_cols - maximum characters per line (> 0) +; 20(a6) = max_rows - maximum number of lines to render (> 0) +; 24(a6) = str_ptr - pointer to null-terminated ASCII string +; 28(a6) = text_color - palette index for text (0-31) +; +; Word-wraps the string to fit within (max_cols x max_rows) character cells. +; Break points: last whitespace (ASCII 0x20) before the column limit. +; If no space is found, a hard break is applied at max_cols. +; Rendering stops when max_rows is exhausted or the string ends. +; +; Register map (all saved on entry/exit): +; a0 = cur_ptr (advances through string) +; a1 = scan ptr (inner scan loop) +; a2 = last_space_ptr (0 = none found this line) +; a3 = end_ptr (exclusive end of segment to print) +; a4 = next_ptr (cur_ptr for next row) +; d2 = col cursor (cx + chars drawn on current line) +; d3 = row cursor (cy + row_index, absolute) +; d4 = max_cols (constant) +; d5 = rows_left +; d6 = text_color (constant) +; d7 = scan column counter +; ============================================================ +DrawWrappedText: + link a6,#0 + movem.l d1-d7/a0-a4,-(sp) + + move.l 20(a6),d5 ; d5 = rows_left + tst.l d5 + ble .dwt_exit + + move.l 16(a6),d4 ; d4 = max_cols + tst.l d4 + ble .dwt_exit + + move.l 28(a6),d6 ; d6 = text_color + move.l 12(a6),d3 ; d3 = row cursor (abs char row = cy initially) + move.l 24(a6),a0 ; a0 = cur_ptr + +.dwt_row_loop: + ; Scan up to max_cols characters looking for last space / NUL + move.l a0,a1 ; a1 = scan pointer + suba.l a2,a2 ; a2 = last_space_ptr = NULL (0) + move.l d4,d7 ; d7 = cols_remaining + +.dwt_scan: + tst.l d7 + beq .dwt_hit_limit ; scanned max_cols chars + moveq #0,d0 + move.b (a1),d0 + tst.b d0 + beq .dwt_hit_nul ; NUL found + cmp.b #$20,d0 ; space? + bne .dwt_no_space + move.l a1,a2 ; record last space position +.dwt_no_space: + addq.l #1,a1 + subq.l #1,d7 + bra .dwt_scan + +.dwt_hit_nul: + ; NUL within max_cols: print a0..a1 (a1 = NUL byte), then done + move.l a1,a3 ; a3 = end_ptr (exclusive: NUL pos) + move.l a1,a4 ; a4 = next_ptr (points to NUL → exits after) + bra .dwt_print + +.dwt_hit_limit: + ; Reached max_cols. Break at last space if available. + move.l a2,d0 + tst.l d0 + beq .dwt_hard_break ; no space found + + ; Soft break: print up to (not including) the space + move.l a2,a3 ; a3 = end_ptr = space position (exclusive) + move.l a2,a4 + addq.l #1,a4 ; a4 = next_ptr = past space + bra .dwt_print + +.dwt_hard_break: + ; No space within max_cols: break at the column limit + move.l a1,a3 ; a3 = end_ptr = a0 + max_cols + move.l a1,a4 ; a4 = next_ptr = same (continue from break) + +.dwt_print: + ; Print chars from a0 to a3 (exclusive) at character position (cx, d3) + move.l 8(a6),d2 ; d2 = column cursor = cx + +.dwt_char: + cmpa.l a3,a0 ; a0 >= end_ptr? + bge .dwt_line_done + + ; Set cursor and draw one character + move.w d2,gfx_text_cursor_x ; column (low word of d2) + move.w d3,gfx_text_cursor_y ; row (low word of d3) + moveq #0,d0 + move.b (a0)+,d0 ; d0 = ASCII char; advance cur_ptr + move.l d6,d1 ; d1 = color + jsr _DrawChar ; draw at cursor; preserves d1-d7,a0-a4 + addq.l #1,d2 ; advance column cursor + bra .dwt_char + +.dwt_line_done: + ; Advance cur_ptr to next_ptr + move.l a4,a0 + ; If cur_ptr now points to NUL (string done), exit + tst.b (a0) + beq .dwt_exit + + ; Move to next row + addq.l #1,d3 ; row_cursor++ + subq.l #1,d5 ; rows_left-- + tst.l d5 + bgt .dwt_row_loop + +.dwt_exit: + moveq #0,d0 + movem.l (sp)+,d1-d7/a0-a4 + unlk a6 + rts + + +; ============================================================ +; DrawMsgBox(x, y, w, h, bg_color, border_color, str_ptr, text_color) +; 8(a6) = x +; 12(a6) = y +; 16(a6) = w - width in pixels (should be multiple of 8) +; 20(a6) = h - height in pixels (should be multiple of 8) +; 24(a6) = bg_color - window background palette index +; 28(a6) = border_color - frame palette index +; 32(a6) = str_ptr - pointer to null-terminated message string +; 36(a6) = text_color - text palette index +; +; Draws a bordered window and renders word-wrapped text inside it. +; Interior text area has 1-character padding on all sides. +; Text units: 1 character = 8 pixels (8x8 font). +; ============================================================ +DrawMsgBox: + link a6,#0 + movem.l d1-d4,-(sp) ; d1-d4 survive nested calls (FillRect/DrawBox preserve them) + + ; ---- Draw the window frame ---- + move.l 28(a6),-(sp) ; border_color (arg6) + move.l 24(a6),-(sp) ; bg_color (arg5) + move.l 20(a6),-(sp) ; h (arg4) + move.l 16(a6),-(sp) ; w (arg3) + move.l 12(a6),-(sp) ; y (arg2) + move.l 8(a6),-(sp) ; x (arg1) + jsr DrawBox + lea 24(sp),sp + + ; ---- Compute text area in character units ---- + ; cx = x/8 + 1 (1-char padding from left border) + move.l 8(a6),d1 + lsr.l #3,d1 + addq.l #1,d1 ; d1 = cx + + ; cy = y/8 + 1 + move.l 12(a6),d2 + lsr.l #3,d2 + addq.l #1,d2 ; d2 = cy + + ; max_cols = w/8 - 2 (subtract left+right 1-char padding) + move.l 16(a6),d3 + lsr.l #3,d3 + subq.l #2,d3 ; d3 = max_cols + + ; max_rows = h/8 - 2 + move.l 20(a6),d4 + lsr.l #3,d4 + subq.l #2,d4 ; d4 = max_rows + + ; Bail if text area is too small + tst.l d3 + ble .dmb_exit + tst.l d4 + ble .dmb_exit + + ; ---- Render word-wrapped text ---- + ; DrawWrappedText(cx, cy, max_cols, max_rows, str_ptr, text_color) + move.l 36(a6),-(sp) ; text_color (arg6) + move.l 32(a6),-(sp) ; str_ptr (arg5) + move.l d4,-(sp) ; max_rows (arg4) + move.l d3,-(sp) ; max_cols (arg3) + move.l d2,-(sp) ; cy (arg2) + move.l d1,-(sp) ; cx (arg1) + jsr DrawWrappedText + lea 24(sp),sp + +.dmb_exit: + moveq #0,d0 + movem.l (sp)+,d1-d4 + unlk a6 + rts + + +; ============================================================ +; DrawGadget(gadget_ptr) +; 8(a6) = gadget_ptr - pointer to a GADGET struct (see gui.i for layout) +; +; Dispatches to the appropriate renderer based on GADGET_TYPE field. +; Type 0 = message box (DrawMsgBox), type 1 = button (DrawButton). +; Unknown types are silently ignored (returns 0). +; ============================================================ +DrawGadget: + link a6,#0 + movem.l d1/a0,-(sp) + + move.l 8(a6),a0 ; a0 = gadget struct pointer + + ; Dispatch on GADGET_TYPE: 0=msgbox, 1=button; skip unknown + move.w 18(a0),d1 ; 18 = GADGET_TYPE offset + tst.w d1 + beq .dg_dispatch ; type 0: msgbox + cmp.w #1,d1 + bne .dg_done ; unknown type: skip + +.dg_dispatch: + ; Push all 8 args (identical layout for DrawMsgBox and DrawButton) + moveq #0,d1 + move.w 16(a0),d1 ; GADGET_TCOLOR (unsigned) + move.l d1,-(sp) ; arg8 = text_color + + move.l 12(a0),-(sp) ; GADGET_TEXT arg7 = str_ptr (long) + + moveq #0,d1 + move.w 10(a0),d1 ; GADGET_BORDER (unsigned) + move.l d1,-(sp) ; arg6 = border_color + + moveq #0,d1 + move.w 8(a0),d1 ; GADGET_BG (unsigned) + move.l d1,-(sp) ; arg5 = bg_color + + moveq #0,d1 + move.w 6(a0),d1 ; GADGET_H (unsigned, positive pixels) + move.l d1,-(sp) ; arg4 = h + + moveq #0,d1 + move.w 4(a0),d1 ; GADGET_W (unsigned, positive pixels) + move.l d1,-(sp) ; arg3 = w + + move.w 2(a0),d1 ; GADGET_Y (signed coordinate) + ext.l d1 ; sign-extend: allows negative y + move.l d1,-(sp) ; arg2 = y + + move.w 0(a0),d1 ; GADGET_X (signed coordinate) + ext.l d1 ; sign-extend: allows negative x + move.l d1,-(sp) ; arg1 = x + + ; Call correct renderer (a0 still valid above pushed args) + tst.w 18(a0) + bne .dg_call_button + jsr DrawMsgBox + bra .dg_pop +.dg_call_button: + jsr DrawButton +.dg_pop: + lea 32(sp),sp + +.dg_done: + moveq #0,d0 + movem.l (sp)+,d1/a0 + unlk a6 + rts + + +; ============================================================ +; DrawButton(x, y, w, h, bg, border, label, tcolor) +; 8(a6) = x - left pixel +; 12(a6) = y - top pixel +; 16(a6) = w - width in pixels (multiple of 8 recommended) +; 20(a6) = h - height in pixels (multiple of 8 recommended) +; 24(a6) = bg - background palette index +; 28(a6) = border - border palette index +; 32(a6) = label - pointer to null-terminated label string +; 36(a6) = tcolor - text palette index +; +; Draws a bordered box with the label centered horizontally and +; vertically (snapped to the 8-pixel character grid). +; Returns d0 = 0. +; ============================================================ +DrawButton: + link a6,#-4 ; -4(a6) = strlen local (long) + movem.l d1-d5/a0,-(sp) + + ; ---- 1. Background fill ---- + move.l 24(a6),-(sp) ; bg + move.l 20(a6),-(sp) ; h + move.l 16(a6),-(sp) ; w + move.l 12(a6),-(sp) ; y + move.l 8(a6),-(sp) ; x + jsr FillRect + lea 20(sp),sp + + ; ---- 2. Top highlight (1 px, border color = bright) ---- + move.l 28(a6),-(sp) ; color = border (highlight) + move.l 16(a6),-(sp) ; w + move.l 12(a6),-(sp) ; y (top edge) + move.l 8(a6),-(sp) ; x + jsr DrawHLine + lea 16(sp),sp + + ; ---- 3. Left highlight (1 px, border color = bright) ---- + move.l 28(a6),-(sp) ; color = border (highlight) + move.l 20(a6),-(sp) ; h + move.l 12(a6),-(sp) ; y + move.l 8(a6),-(sp) ; x + jsr DrawVLine + lea 16(sp),sp + + ; ---- 4. Bottom shadow (1 px, black = color 0) ---- + move.l #0,-(sp) ; color = 0 (shadow) + move.l 16(a6),-(sp) ; w + move.l 20(a6),d1 + add.l 12(a6),d1 + subq.l #1,d1 ; d1 = y + h - 1 + move.l d1,-(sp) ; y = bottom edge + move.l 8(a6),-(sp) ; x + jsr DrawHLine + lea 16(sp),sp + + ; ---- 5. Right shadow (1 px, black = color 0) ---- + move.l #0,-(sp) ; color = 0 (shadow) + move.l 20(a6),-(sp) ; h + move.l 12(a6),-(sp) ; y + move.l 16(a6),d1 + add.l 8(a6),d1 + subq.l #1,d1 ; d1 = x + w - 1 + move.l d1,-(sp) ; x = right edge + jsr DrawVLine + lea 16(sp),sp + + ; ---- 6. Measure label (strlen) ---- + move.l 32(a6),a0 ; label ptr + clr.l -4(a6) ; len = 0 +.dbt_len: + tst.b (a0)+ + beq .dbt_len_done + addq.l #1,-4(a6) + bra .dbt_len +.dbt_len_done: + tst.l -4(a6) + beq .dbt_done ; empty label: skip rendering + + ; ---- 7. Horizontal centering ---- + ; cx = (x + (w - len*8) / 2) / 8 + move.l -4(a6),d1 ; len + lsl.l #3,d1 ; len * 8 (pixel width of label) + move.l 16(a6),d2 ; w + sub.l d1,d2 ; d2 = w - label_px + tst.l d2 + bge .dbt_hpad_ok + clr.l d2 ; clamp: label wider than button +.dbt_hpad_ok: + lsr.l #1,d2 ; d2 = (w - label_px) / 2 + add.l 8(a6),d2 ; d2 = x + hpad_px + lsr.l #3,d2 ; d2 = cx (snap to char grid) + move.l d2,d1 ; d1 = cx + + ; ---- 8. Vertical centering ---- + ; cy = (y + h/2) / 8 - rounds text row to nearest char row + move.l 20(a6),d2 ; h + lsr.l #1,d2 ; h/2 + add.l 12(a6),d2 ; y + h/2 + lsr.l #3,d2 ; d2 = cy (char row) + + ; ---- 9. Render label one char at a time ---- + move.w d1,gfx_text_cursor_x + move.w d2,gfx_text_cursor_y + move.l 32(a6),a0 ; reset to label start + move.l 36(a6),d5 ; d5 = tcolor (constant) +.dbt_draw: + moveq #0,d0 + move.b (a0)+,d0 ; d0 = next ASCII char; NUL = stop + tst.b d0 + beq .dbt_done + move.l d5,d1 ; d1 = color (_DrawChar: D0=char, D1=color) + jsr _DrawChar + move.w gfx_text_cursor_x,d4 + addq.w #1,d4 + move.w d4,gfx_text_cursor_x + bra .dbt_draw + +.dbt_done: + moveq #0,d0 + movem.l (sp)+,d1-d5/a0 + unlk a6 + rts + + +; ============================================================ +; GuiPollMouse() +; Must be called once per frame AFTER ReadMouse(). +; Reads delta movement via GetMouseDX/GetMouseDY and accumulates +; into gui_abs_mouse_x/y (x clamped to screen width, y clamped +; to 0..255). Reads button via GetMouseLBtn and writes the +; leading-edge (0->1 transition) result into gui_lbtn_edge. +; Returns d0 = 0. +; ============================================================ +GuiPollMouse: + link a6,#0 + movem.l d1-d2,-(sp) + + ; ---- Accumulate X ---- + jsr GetMouseDX ; d0 = dx (signed long) + move.w gui_abs_mouse_x,d1 + ext.l d1 ; d1 = current abs_x + add.l d0,d1 ; d1 = new abs_x + tst.l d1 + bge .gpm_x_pos + moveq #0,d1 ; underflow: clamp to 0 + bra .gpm_x_done +.gpm_x_pos: + move.w gfx_current_mode,d2 + tst.w d2 + bne .gpm_x_hires + cmp.l #319,d1 ; mode 0: max x = 319 + ble .gpm_x_done + move.l #319,d1 + bra .gpm_x_done +.gpm_x_hires: + cmp.l #639,d1 ; mode 1: max x = 639 + ble .gpm_x_done + move.l #639,d1 +.gpm_x_done: + move.w d1,gui_abs_mouse_x + + ; ---- Accumulate Y ---- + jsr GetMouseDY ; d0 = dy (signed long) + move.w gui_abs_mouse_y,d1 + ext.l d1 + add.l d0,d1 + tst.l d1 + bge .gpm_y_pos + moveq #0,d1 + bra .gpm_y_done +.gpm_y_pos: + cmp.l #255,d1 + ble .gpm_y_done + move.l #255,d1 +.gpm_y_done: + move.w d1,gui_abs_mouse_y + + ; ---- Left-button edge detect ---- + jsr GetMouseLBtn ; d0 = current lbtn (0 or 1, long) + move.w d0,d2 ; d2 = current state + moveq #0,d0 ; d0 = edge = 0 (default: no new click) + cmp.w #1,d2 + bne .gpm_no_edge ; not pressed: no edge + tst.w gui_lbtn_prev + bne .gpm_no_edge ; was held last frame: edge already consumed + moveq #1,d0 ; 0->1 transition: click! +.gpm_no_edge: + move.w d0,gui_lbtn_edge + move.w d2,gui_lbtn_prev ; save current state for next frame + + moveq #0,d0 + movem.l (sp)+,d1-d2 + unlk a6 + rts + + +; ============================================================ +; GuiHitTest(gadget_ptr) -> int +; 8(a6) = gadget_ptr - pointer to GADGET struct +; Returns 1 if gui_lbtn_edge is set AND the absolute mouse +; position falls inside [gx..gx+gw) x [gy..gy+gh). +; Returns 0 otherwise. Works for any gadget type. +; ============================================================ +GuiHitTest: + link a6,#0 + movem.l d1-d5/a0,-(sp) + + tst.w gui_lbtn_edge + beq .ght_miss + + move.l 8(a6),a0 ; gadget_ptr + move.w 0(a0),d1 ; GADGET_X (signed) + ext.l d1 + move.w 2(a0),d2 ; GADGET_Y (signed) + ext.l d2 + moveq #0,d3 + move.w 4(a0),d3 ; GADGET_W (unsigned) + moveq #0,d4 + move.w 6(a0),d4 ; GADGET_H (unsigned) + + move.w gui_abs_mouse_x,d5 + ext.l d5 ; mx + cmp.l d1,d5 ; mx < gx? + blt .ght_miss + move.l d1,d0 + add.l d3,d0 ; d0 = gx + gw + cmp.l d0,d5 ; mx >= gx+gw? + bge .ght_miss + + move.w gui_abs_mouse_y,d5 + ext.l d5 ; my + cmp.l d2,d5 ; my < gy? + blt .ght_miss + move.l d2,d0 + add.l d4,d0 ; d0 = gy + gh + cmp.l d0,d5 ; my >= gy+gh? + bge .ght_miss + + moveq #1,d0 + movem.l (sp)+,d1-d5/a0 + unlk a6 + rts +.ght_miss: + moveq #0,d0 + movem.l (sp)+,d1-d5/a0 + unlk a6 + rts + + +; ============================================================ +; GuiHitTestRect(x, y, w, h) -> int +; 8(a6)=x, 12(a6)=y, 16(a6)=w, 20(a6)=h (all longs, pixels) +; Returns 1 if gui_lbtn_edge AND abs mouse in [x..x+w)x[y..y+h). +; Convenience alternative to GuiHitTest for HAS callers that +; use DrawButton directly without a GADGET struct. +; ============================================================ +GuiHitTestRect: + link a6,#0 + movem.l d1-d5,-(sp) + + tst.w gui_lbtn_edge + beq .ghr_miss + + move.l 8(a6),d1 ; x + move.l 12(a6),d2 ; y + move.l 16(a6),d3 ; w + move.l 20(a6),d4 ; h + + move.w gui_abs_mouse_x,d5 + ext.l d5 ; mx + cmp.l d1,d5 + blt .ghr_miss + move.l d1,d0 + add.l d3,d0 + cmp.l d0,d5 + bge .ghr_miss + + move.w gui_abs_mouse_y,d5 + ext.l d5 ; my + cmp.l d2,d5 + blt .ghr_miss + move.l d2,d0 + add.l d4,d0 + cmp.l d0,d5 + bge .ghr_miss + + moveq #1,d0 + movem.l (sp)+,d1-d5 + unlk a6 + rts +.ghr_miss: + moveq #0,d0 + movem.l (sp)+,d1-d5 + unlk a6 + rts + + +; ============================================================ +; GetGuiMouseX() -> int +; Returns gui_abs_mouse_x (accumulated clamped X pixel) as a signed long. +; No stack frame needed - no arguments. +; ============================================================ +GetGuiMouseX: + move.w gui_abs_mouse_x,d0 + ext.l d0 + rts + +; ============================================================ +; GetGuiMouseY() -> int +; Returns gui_abs_mouse_y (accumulated clamped Y pixel) as a signed long. +; No stack frame needed - no arguments. +; ============================================================ +GetGuiMouseY: + move.w gui_abs_mouse_y,d0 + ext.l d0 + rts + + +; ============================================================ +; Bit-mask lookup tables (read-only, pc-relative accessible) +; +; gui_lmask[i] = 0xFF >> i +; Pixels from bit-position i to 7 within a byte (right side of byte). +; Used for the leftmost (partial) byte of a filled span. +; +; gui_rmask[i] = top (i+1) bits of a byte +; Pixels from bit-position 0 to i within a byte (left side of byte). +; Used for the rightmost (partial) byte of a filled span. +; +; Amiga bit numbering: bit 7 = leftmost pixel, bit 0 = rightmost pixel. +; ============================================================ + SECTION gui_data,DATA + +gui_lmask: + dc.b $FF,$7F,$3F,$1F,$0F,$07,$03,$01 + +gui_rmask: + dc.b $80,$C0,$E0,$F0,$F8,$FC,$FE,$FF + +; Mouse event state (updated by GuiPollMouse each frame) +gui_abs_mouse_x: dc.w 0 ; accumulated absolute X pixel (clamped to screen width-1) +gui_abs_mouse_y: dc.w 0 ; accumulated absolute Y pixel (clamped to 0..255) +gui_lbtn_prev: dc.w 0 ; left-button state from previous GuiPollMouse call +gui_lbtn_edge: dc.w 0 ; 1 on the frame the left button is first pressed, else 0 diff --git a/scripts/build_msgbox_demo.sh b/scripts/build_msgbox_demo.sh new file mode 100755 index 0000000..643b104 --- /dev/null +++ b/scripts/build_msgbox_demo.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# build_msgbox_demo.sh +# Compile examples/msgbox_demo.has through the full pipeline: +# HAS → .s → vasm per-object → vlink → build/msgbox_demo.exe +# +# Usage: +# ./scripts/build_msgbox_demo.sh # from project root +# +# Requirements: +# - Python venv activated (or python/hasc on PATH) +# - vasmm68k_mot on PATH, or set VASM env variable +# - vlink on PATH + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Paths +# --------------------------------------------------------------------------- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD="$ROOT/build" +LIB="$ROOT/lib" +SRC="$ROOT/examples/msgbox_demo.has" + +# --------------------------------------------------------------------------- +# Tool detection +# --------------------------------------------------------------------------- +# Allow override: VASM=/path/to/vasmm68k_mot ./scripts/build_msgbox_demo.sh +VASM="${VASM:-vasmm68k_mot}" + +if ! command -v "$VASM" &>/dev/null; then + # Fall back to the bundled vasm from the Amiga Assembly VS Code extension + BUNDLED="$HOME/.vscode/extensions/prb28.amiga-assembly-1.8.13/resources/bin/linux/vasmm68k_mot" + if [[ -x "$BUNDLED" ]]; then + VASM="$BUNDLED" + else + echo "ERROR: vasmm68k_mot not found. Set VASM=/path/to/vasmm68k_mot." >&2 + exit 1 + fi +fi + +if ! command -v vlink &>/dev/null; then + echo "ERROR: vlink not found. Install vlink and add it to PATH." >&2 + exit 1 +fi + +VASM_FLAGS=(-Fhunk -devpac -I "$LIB") + +mkdir -p "$BUILD" + +echo "=== Build: msgbox_demo ===" +echo " Root : $ROOT" +echo " VASM : $VASM" +echo "" + +# --------------------------------------------------------------------------- +# Step 1: HAS → assembly +# --------------------------------------------------------------------------- +echo "[1/3] Compiling HAS source..." +(cd "$ROOT" && python -m hasc.cli "$SRC" -o "$BUILD/msgbox_demo.s") +echo " -> build/msgbox_demo.s" + +# --------------------------------------------------------------------------- +# Step 2: Assemble every object +# --------------------------------------------------------------------------- +echo "[2/3] Assembling objects..." + +assemble() { + local src="$1" obj="$2" + echo " $src -> $(basename "$obj")" + "$VASM" "${VASM_FLAGS[@]}" "$src" -o "$obj" +} + +assemble "$BUILD/msgbox_demo.s" "$BUILD/msgbox_demo.o" +assemble "$LIB/gui.s" "$BUILD/gui.o" +assemble "$LIB/graphics.s" "$BUILD/graphics.o" +assemble "$LIB/sprite.s" "$BUILD/sprite.o" +assemble "$LIB/font8x8.s" "$BUILD/font8x8.o" +assemble "$LIB/helpers.s" "$BUILD/helpers.o" +assemble "$LIB/takeover.s" "$BUILD/takeover.o" +assemble "$LIB/input.s" "$BUILD/input.o" + +# --------------------------------------------------------------------------- +# Step 3: Link +# --------------------------------------------------------------------------- +echo "[3/3] Linking..." +vlink -bamigahunk \ + "$BUILD/msgbox_demo.o" \ + "$BUILD/gui.o" \ + "$BUILD/graphics.o" \ + "$BUILD/sprite.o" \ + "$BUILD/font8x8.o" \ + "$BUILD/helpers.o" \ + "$BUILD/takeover.o" \ + "$BUILD/input.o" \ + -o "$BUILD/msgbox_demo.exe" + +echo "" +echo "=== Done: build/msgbox_demo.exe ==="