Skip to content

Instantly share code, notes, and snippets.

@ISSOtm
Last active March 30, 2020 18:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ISSOtm/4d251fbc3cecec13eab113645160bf90 to your computer and use it in GitHub Desktop.
Save ISSOtm/4d251fbc3cecec13eab113645160bf90 to your computer and use it in GitHub Desktop.
Menu system, designed for Motherboard GB, and ported elsewhere...
SECTION "Language menu header", ROMX
LanguageMenuHeader::
db BANK("Language menu")
dw LanguageMenuInit
db PADF_START | PADF_DOWN | PADF_UP
db 0 ; Prevent repeat press
dw 0, 0, 0, ForceMenuValidation, 0, 0, 0, 0
db 0 ; Previous item
db 1 ; Allow wrapping
db 0 ; Default item
db NB_LANGUAGES ; Size
dw LanguageMenuRedraw
dw LanguageMenuItems
dw 0
SECTION "Language menu", ROMX
LanguageMenuInit:
rst wait_vblank
xor a
ldh [rLCDC], a
; xor a
ld [wTextLetterDelay], a
ld hl, _SCRN0
ld bc, SCRN_VX_B * SCRN_Y_B
; xor a
rst memset
ld a, SCRN_X
ld [wTextLineLength], a
ld a, SCRN_Y_B
ld [wTextNbLines], a
ld [wTextRemainingLines], a
ld [wNewlinesUntilFull], a
; a is non-zero
ld hl, LanguageMenuItems
ld b, BANK(LanguageMenuItems)
call PrintVWFText
ld hl, _SCRN0 + SCRN_VY_B * 6 + 7
call SetPenPosition
call PrintVWFChar
; Load extra gfx
; Cursor, Up, Down and START button prompts
ld de, .gfx
ld hl, $8800
ld b, 8
call pb16_unpack_block
ld a, 42
ldh [hLangSelMenuTimer1], a
xor a
ldh [hLangSelMenuTimer2], a
; xor a
ldh [hBGP], a
ldh [hOBP0], a
ld hl, wShadowOAM + $A0 - 1
; xor a
.clearSprites
dec l ; dec hl
dec l ; dec hl
dec l ; dec hl
ld [hld], a
jr nz, .clearSprites
; xor a
ldh [hLangSelMenuCursorPos], a
inc hl
ld a, h ; ld a, HIGH(wShadowOAM)
ldh [hOAMBufferHigh], a
; ld de, .oam
ld hl, wShadowOAM
ld c, .palettePacket - .oam
rst memcpy_small
ld a, $1B
ldh [hLangSelMenuPalette], a
ld a, LCDCF_ON | LCDCF_WINOFF | LCDCF_BG8000 | LCDCF_BG9800 | LCDCF_OBJ8 | LCDCF_OBJON | LCDCF_BGON
ldh [hLCDC], a
ldh [rLCDC], a
ld hl, .palettePacket
ldh a, [hIsSGB]
and a
call nz, SendPacketNoDelay
jp DrawVWFChars
.gfx
INCBIN "res/lang_screen/gfx.chr.pb16"
.oam
dspr 6 * 8 - 2, 6 * 8, $80, 0
dspr 6 * 8 - 10, 6 * 8, $82, 0
dspr 6 * 8 + 6, 6 * 8, $82, OAMF_YFLIP
dspr 11 * 8 , 9 * 8, $83, 0
dspr 12 * 8 + 3, 8 * 8, $85, 0
dspr 12 * 8 + 3, 9 * 8, $86, 0
dspr 12 * 8 + 3, 10 * 8, $87, 0
.palettePacket
sgb_packet PAL_SET, 1, 4,0, 4,0, 4,0, 4,0, 1 | $80
LanguageMenuRedraw: ; Called with selected item in `b`
ld a, HIGH(wShadowOAM)
ldh [hOAMBufferHigh], a
; Change flag cel
ldh a, [hLangSelMenuTimer1]
inc a
and $1F
ldh [hLangSelMenuTimer1], a
jr nz, .keepFlagFrame
ld a, [wShadowOAM + 2]
xor 1
ld [wShadowOAM + 2], a
xor a ; Force next event to trigger, as it's supposed to as well
.keepFlagFrame
; Apply fade-in (reusing previous timer for efficieny)
and $03
jr nz, .paletteOK
ldh a, [hBGP]
ld e, a
ldh a, [hLangSelMenuPalette]
add a, a
jr z, .paletteOK
rl e
add a, a
ldh [hLangSelMenuPalette], a
ld a, e
rla
ldh [hBGP], a
ldh [hOBP0], a
.paletteOK
; Make arrows blink
ldh a, [hLangSelMenuTimer2]
inc a
and $1F
ldh [hLangSelMenuTimer2], a
and $18
jr z, .hideArrows
ld a, 6 * 8 + 8
.hideArrows
ld [wShadowOAM + 5], a
ld [wShadowOAM + 9], a
; Change START button cel
ldh a, [hLangSelMenuTimer2]
and $07
jr nz, .keepSTARTFrame
ld a, [wShadowOAM + 14]
xor 7
ld [wShadowOAM + 14], a
.keepSTARTFrame
; Move cursor (and attached sprites as well)
ldh a, [hLangSelMenuCursorPos]
ld c, a
ld a, b ; Selected item
ld [wLanguage], a
add a, a
add a, a
add a, a
sub c
ret z
sra a ; Halve the difference to lerp a bit
jr nz, .ok
inc a
.ok
add a, c
ldh [hLangSelMenuCursorPos], a
add a, 6 * 8 - 2 + 16
ld [wShadowOAM], a
sub 8
ld [wShadowOAM + 4], a
add a, 16
ld [wShadowOAM + 8], a
ret
SECTION "Language menu strings", ROMX
LanguageMenuItems:
db TEXT_BLANKS,4, "ENGLISH\n"
db TEXT_BLANKS,3, "ESPANOL\n"
db "FRANCAIS\n"
db TEXT_BLANKS,3, "NIHONGO"
db 0
SECTION "Menu system", ROM0
; Adds a menu on top of the menu stack
; @param de A pointer to the menu's header in ROM
; @param b The bank where the menu's header is located
; @destroy Loaded ROM bank
AddMenu::
ld a, b
rst bankswitch
ld hl, wNbMenus
ld a, [hl]
cp MENU_STACK_CAPACITY
call nc, MenuStackOverflowError
inc a
ld [hli], a
; ld hl, wMenu0
ld bc, sizeof_Menu
dec a
call GetNthStruct
push hl
ld c, Menu_ROMSize
rst memcpy_small
xor a
ld c, sizeof_Menu - Menu_ROMSize
rst memset_small
pop hl
ld a, [hli] ; Get bank
rst bankswitch
; Run init func
ld a, [hli]
ld h, [hl]
ld l, a
or h
ret z
rst call_hl
ret
; Processes one frame of the menu stack (thus, the topmost)
ProcessMenus::
; By default, no menu is closing
xor a
ld [wMenuClosingReason], a
ld a, [wNbMenus]
and a
ret z
ld hl, wMenu0
ld bc, sizeof_Menu
dec a
call GetNthStruct
ld a, [hli]
rst bankswitch
inc hl
inc hl ; Skip init func
; Get buttons
ld a, [hli] ; Read button mask
ld c, a
ldh a, [hHeldButtons]
and c
ld c, a ; Stash this, and DON'T modify it
; Try to perform RepeatPress
ld a, l ; Get ptr to RepeatPressCounter (will be used even if RepeatPress is disabled)
add a, Menu_RepeatPressCounter - Menu_EnableRepeatPress
ld e, a
adc a, h
sub e
ld d, a
ld b, 0 ; Start by supposing no button will be RepeatPress'd
; If any (non-ignored) button is pressed, stop RepeatPress
ldh a, [hPressedButtons]
and c ; It's fine to do this, since a pressed button is held anyways
jr z, .dontResetRepeatPress
xor a
ld [de], a
.dontResetRepeatPress
ld a, [hli] ; Read EnableRepeatPress
and a
jr z, .skipRepeatPress
ld a, c ; Get held buttons
and PADF_DOWN | PADF_UP | PADF_LEFT | PADF_RIGHT ; Get d-pad only
; Check if exactly 1 direction is being held
jr z, .skipRepeatPress ; If 0, don't do anything
.getFirstDirection
add a, a
jr nc, .getFirstDirection ; Keep going until the first bit is shifted out
jr nz, .skipRepeatPress ; If more bits remain, more than 1 button is being held, so skip
; So, only 1 direction is being held, and if it has just been pressed, the state is currently zero
; Increment the counter
ld a, [de]
inc a
ld [de], a
cp 22
jr c, .skipRepeatPress ; Unless the counter reaches 30, don't do anything
ld a, 20
ld [de], a ; Reset counter (to apply delay)
ld a, c
and PADF_DOWN | PADF_UP | PADF_LEFT | PADF_RIGHT
ld b, a ; Mark this button as RepeatPress'd
.skipRepeatPress
inc de ; Skip RepeatPressCounter
; de = MiscState
; hl = ButtonHooks
ld a, l
add a, 2 * 8 ; Get to the end of the ButtonHooks buffer
ld l, a
jr nc, .nocarry
inc h
.nocarry
; Save currently selected item
inc hl ; Skip prev
inc hl ; Skip flags
ld a, [hld] ; Read cur
dec hl ; Skip flags
ld [hli], a ; Store cur into prev
; hl = AllowWrapping
; Check if a button has been pressed
ldh a, [hPressedButtons]
and c ; Get only those that we are interested in
jr nz, .buttonPressed
; Check if a button is being RepeatPressed
ld a, b
and a
jr z, .noButtonPressed
.buttonPressed
push bc
push de
push hl
ld b, 0
; Look for the button we're gonna process
; hl is 1 byte past the end of the buffer, so 2 decs will put it at the high byte of the last entry
.selectHook
dec hl
dec hl
inc b
add a, a
jr nc, .selectHook
; Load the default menu action
ld a, b
ld [wMenuAction], a
ld a, [hld] ; Read high byte
ld l, [hl]
ld h, a
or l
jr z, .skipHook
; The hook may choose to override the menu action, if it wishes to do so
rst call_hl
.skipHook
pop hl ; Get back ptr to AllowWrapping
ld a, [wMenuAction]
dec a
cp MENU_ACTION_INVALID - 1
jr nc, .menuActionNone
; Perform the requested action
push hl
add a, a
add a, LOW(.menuActions)
ld e, a
adc a, HIGH(.menuActions)
sub e
ld d, a
ld a, [de]
ld b, a
inc de
ld a, [de]
ld d, a
ld e, b
call CallDE
pop hl
.menuActionNone
pop de
pop bc
.noButtonPressed
inc hl ; Skip flags
ld a, [hli] ; Get current item
ld b, a
inc hl ; Skip size
; Run redraw func (if any)
ld a, [hli]
push hl
ld h, [hl]
ld l, a
or h
jr z, .noRedraw
rst call_hl
.noRedraw
pop hl
inc hl
; Skip items ptr
inc hl
inc hl
; Check if this menu should close
ld a, [wMenuClosingReason]
and a
ret z ; jr z, .dontClose
ld a, [hli]
ld h, [hl]
ld l, a
or h
jr z, .noCloseHook
rst call_hl
.noCloseHook
ld hl, wNbMenus
dec [hl]
ld a, [wMenuClosingReason]
ld [wPreviousMenuClosingReason], a
.dontClose
ret
.menuActions
dw MenuMoveDown ; DOWN
dw MenuMoveUp ; UP
dw MenuDoNothing ; LEFT
dw MenuDoNothing ; RIGHT
dw MenuDoNothing ; START
dw MenuDoNothing ; SELECT
dw MenuCancel ; B
dw MenuValidate ; A
dw MenuAddNew
MenuMoveDown:
ld a, [hli] ; Enable wrapping?
ld e, a
ld a, [hli] ; Current item
inc a ; Move 1 down
cp [hl] ; Compare to size
jr c, .ok
ld a, e
rra ; Get bit 0 into carry
ret nc ; If wrapping is disabled, do nothing
xor a ; Wrap back to 0
.ok
dec hl ; Get back to current item
ld [hl], a ; Write back
ret
MenuMoveUp:
ld a, [hli] ; Enable wrapping?
ld e, a
ld a, [hl] ; Current item
and a ; Are we about to wrap?
jr nz, .ok ; No, carry on
ld a, e
rra ; Get bit 0 into carry
ret nc ; If wrapping is disabled, do nothing
inc hl
ld a, [hld]
.ok
dec a
ld [hl], a
ret
MenuValidate:
inc hl
ld a, [hl] ; Current item
ld [wPreviousMenuItem], a
ld a, MENU_VALIDATED
db $11 ; ld de, XXXX
MenuCancel:
ld a, MENU_CANCELLED
ld [wMenuClosingReason], a
MenuDoNothing: ; Stub for menu actions that do nothing
ret
; One of the menu actions
MenuAddNew:
ld hl, wMenuAction+1
ld a, [hli]
ld b, a
ld a, [hli]
ld d, [hl]
ld e, a
ldh a, [hCurROMBank]
push af
call AddMenu
pop af
rst bankswitch
ret
ForceMenuValidation::
ld a, MENU_ACTION_VALIDATE
ld [wMenuAction], a
ret
PreventMenuAction::
xor a
ld [wMenuAction], a
ret
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment