Update "UI-Widget-Patterns.-"
parent
45cee16081
commit
3cbfcf060b
1 changed files with 395 additions and 404 deletions
|
|
@ -1,404 +1,395 @@
|
||||||
# UI Widget Patterns
|
# UI Widget Patterns
|
||||||
|
|
||||||
Reusable patterns for building menus, dialogs, and HUD elements using Frame, Caption, and Sprite components. These patterns work independently of Grids.
|
Reusable patterns for building menus, dialogs, and HUD elements using Frame, Caption, and Sprite components. These patterns work independently of Grids.
|
||||||
|
|
||||||
**Related Pages:**
|
**Related Pages:**
|
||||||
- [[UI-Component-Hierarchy]] - Base UI components
|
- [[UI-Component-Hierarchy]] - Base UI components
|
||||||
- [[Input-and-Events]] - Event handler reference
|
- [[Input-and-Events]] - Event handler reference
|
||||||
- [[Animation-System]] - Animating widget transitions
|
- [[Animation-System]] - Animating widget transitions
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Setup Template
|
## Setup Template
|
||||||
|
|
||||||
Most widget patterns fit into this basic structure:
|
Most widget patterns fit into this basic structure:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
|
|
||||||
# Create scene and get UI collection
|
# Create scene and get UI collection
|
||||||
mcrfpy.createScene("menu")
|
scene = mcrfpy.Scene("menu")
|
||||||
ui = mcrfpy.sceneUI("menu")
|
ui = scene.children
|
||||||
|
|
||||||
# Load assets
|
# Create root container for the menu/HUD
|
||||||
font = mcrfpy.Font("assets/fonts/mono.ttf")
|
root = mcrfpy.Frame(pos=(50, 50), size=(300, 400),
|
||||||
# texture = mcrfpy.Texture("assets/ui_sprites.png", grid_size=(16, 16))
|
fill_color=mcrfpy.Color(30, 30, 40))
|
||||||
|
root.outline_color = mcrfpy.Color(80, 80, 100)
|
||||||
# Create root container for the menu/HUD
|
root.outline = 2
|
||||||
root = mcrfpy.Frame(pos=(50, 50), size=(300, 400))
|
ui.append(root)
|
||||||
root.fill_color = mcrfpy.Color(30, 30, 40)
|
|
||||||
root.outline_color = mcrfpy.Color(80, 80, 100)
|
# Add widgets to root.children...
|
||||||
root.outline = 2
|
|
||||||
ui.append(root)
|
mcrfpy.current_scene = scene
|
||||||
|
```
|
||||||
# Add widgets to root.children...
|
|
||||||
|
---
|
||||||
mcrfpy.setScene("menu")
|
|
||||||
```
|
## Button
|
||||||
|
|
||||||
---
|
A clickable frame with label and hover feedback.
|
||||||
|
|
||||||
## Button
|
```python
|
||||||
|
def make_button(parent, pos, text, on_click):
|
||||||
A clickable frame with label and hover feedback.
|
"""Create a button with hover effects."""
|
||||||
|
btn = mcrfpy.Frame(pos=pos, size=(120, 32),
|
||||||
```python
|
fill_color=mcrfpy.Color(60, 60, 80))
|
||||||
def make_button(parent, pos, text, on_click):
|
btn.outline_color = mcrfpy.Color(100, 100, 140)
|
||||||
"""Create a button with hover effects."""
|
btn.outline = 1
|
||||||
btn = mcrfpy.Frame(pos=pos, size=(120, 32))
|
|
||||||
btn.fill_color = mcrfpy.Color(60, 60, 80)
|
label = mcrfpy.Caption(text=text, pos=(10, 6))
|
||||||
btn.outline_color = mcrfpy.Color(100, 100, 140)
|
label.fill_color = mcrfpy.Color(220, 220, 220)
|
||||||
btn.outline = 1
|
btn.children.append(label)
|
||||||
|
|
||||||
label = mcrfpy.Caption(pos=(10, 6), text=text)
|
# Hover effects - on_enter/on_exit receive (pos: Vector)
|
||||||
label.fill_color = mcrfpy.Color(220, 220, 220)
|
btn.on_enter = lambda pos: setattr(btn, 'fill_color',
|
||||||
btn.children.append(label)
|
mcrfpy.Color(80, 80, 110))
|
||||||
|
btn.on_exit = lambda pos: setattr(btn, 'fill_color',
|
||||||
# Hover effects
|
mcrfpy.Color(60, 60, 80))
|
||||||
def on_enter():
|
|
||||||
btn.fill_color = mcrfpy.Color(80, 80, 110)
|
# Click handler receives (pos: Vector, button: MouseButton, action: InputState)
|
||||||
btn.outline_color = mcrfpy.Color(140, 140, 180)
|
def handle_click(pos, button, action):
|
||||||
|
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
|
||||||
def on_exit():
|
on_click()
|
||||||
btn.fill_color = mcrfpy.Color(60, 60, 80)
|
btn.on_click = handle_click
|
||||||
btn.outline_color = mcrfpy.Color(100, 100, 140)
|
|
||||||
|
parent.children.append(btn)
|
||||||
btn.on_enter = on_enter
|
return btn
|
||||||
btn.on_exit = on_exit
|
|
||||||
btn.on_click = lambda x, y, btn: on_click()
|
# Usage
|
||||||
|
make_button(root, (20, 20), "New Game", lambda: start_new_game())
|
||||||
parent.children.append(btn)
|
make_button(root, (20, 60), "Options", lambda: show_options())
|
||||||
return btn
|
make_button(root, (20, 100), "Quit", lambda: sys.exit(0))
|
||||||
|
```
|
||||||
# Usage
|
|
||||||
make_button(root, (20, 20), "New Game", lambda: mcrfpy.setScene("game"))
|
---
|
||||||
make_button(root, (20, 60), "Options", lambda: show_options())
|
|
||||||
make_button(root, (20, 100), "Quit", lambda: mcrfpy.exit())
|
## Toggle / Checkbox
|
||||||
```
|
|
||||||
|
A button that toggles state and updates its appearance.
|
||||||
---
|
|
||||||
|
```python
|
||||||
## Toggle / Checkbox
|
def make_toggle(parent, pos, label_text, initial=False, on_change=None):
|
||||||
|
"""Create a toggle with visual state indicator."""
|
||||||
A button that toggles state and updates its appearance.
|
state = {"checked": initial}
|
||||||
|
|
||||||
```python
|
frame = mcrfpy.Frame(pos=pos, size=(160, 28),
|
||||||
def make_toggle(parent, pos, label_text, initial=False, on_change=None):
|
fill_color=mcrfpy.Color(40, 40, 50))
|
||||||
"""Create a toggle with visual state indicator."""
|
|
||||||
state = {"checked": initial}
|
# Checkbox indicator
|
||||||
|
indicator = mcrfpy.Frame(pos=(6, 6), size=(16, 16))
|
||||||
frame = mcrfpy.Frame(pos=pos, size=(160, 28))
|
indicator.outline = 1
|
||||||
frame.fill_color = mcrfpy.Color(40, 40, 50)
|
indicator.outline_color = mcrfpy.Color(120, 120, 140)
|
||||||
|
frame.children.append(indicator)
|
||||||
# Checkbox indicator
|
|
||||||
indicator = mcrfpy.Frame(pos=(6, 6), size=(16, 16))
|
# Label
|
||||||
indicator.outline = 1
|
label = mcrfpy.Caption(text=label_text, pos=(30, 5))
|
||||||
indicator.outline_color = mcrfpy.Color(120, 120, 140)
|
label.fill_color = mcrfpy.Color(200, 200, 200)
|
||||||
frame.children.append(indicator)
|
frame.children.append(label)
|
||||||
|
|
||||||
# Label
|
def update_visual():
|
||||||
label = mcrfpy.Caption(pos=(30, 5), text=label_text)
|
if state["checked"]:
|
||||||
label.fill_color = mcrfpy.Color(200, 200, 200)
|
indicator.fill_color = mcrfpy.Color(100, 180, 100)
|
||||||
frame.children.append(label)
|
else:
|
||||||
|
indicator.fill_color = mcrfpy.Color(50, 50, 60)
|
||||||
def update_visual():
|
|
||||||
if state["checked"]:
|
def toggle(pos, button, action):
|
||||||
indicator.fill_color = mcrfpy.Color(100, 180, 100)
|
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
|
||||||
else:
|
state["checked"] = not state["checked"]
|
||||||
indicator.fill_color = mcrfpy.Color(50, 50, 60)
|
update_visual()
|
||||||
|
if on_change:
|
||||||
def toggle(x, y, btn):
|
on_change(state["checked"])
|
||||||
state["checked"] = not state["checked"]
|
|
||||||
update_visual()
|
frame.on_click = toggle
|
||||||
if on_change:
|
update_visual()
|
||||||
on_change(state["checked"])
|
|
||||||
|
parent.children.append(frame)
|
||||||
frame.on_click = toggle
|
return state # Return state dict for external access
|
||||||
update_visual()
|
|
||||||
|
# Usage
|
||||||
parent.children.append(frame)
|
music_toggle = make_toggle(root, (20, 20), "Music", initial=True,
|
||||||
return state # Return state dict for external access
|
on_change=lambda on: set_music_volume(1.0 if on else 0.0))
|
||||||
|
```
|
||||||
# Usage
|
|
||||||
music_toggle = make_toggle(root, (20, 20), "Music", initial=True,
|
---
|
||||||
on_change=lambda on: set_music_volume(1.0 if on else 0.0))
|
|
||||||
```
|
## Vertical Menu
|
||||||
|
|
||||||
---
|
A list of selectable options with keyboard navigation support.
|
||||||
|
|
||||||
## Vertical Menu
|
```python
|
||||||
|
class VerticalMenu:
|
||||||
A list of selectable options with keyboard navigation support.
|
def __init__(self, parent, pos, options, on_select):
|
||||||
|
"""
|
||||||
```python
|
options: list of (label, value) tuples
|
||||||
class VerticalMenu:
|
on_select: callback(value) when option chosen
|
||||||
def __init__(self, parent, pos, options, on_select):
|
"""
|
||||||
"""
|
self.options = options
|
||||||
options: list of (label, value) tuples
|
self.on_select = on_select
|
||||||
on_select: callback(value) when option chosen
|
self.selected = 0
|
||||||
"""
|
|
||||||
self.options = options
|
self.frame = mcrfpy.Frame(pos=pos,
|
||||||
self.on_select = on_select
|
size=(180, len(options) * 28 + 8),
|
||||||
self.selected = 0
|
fill_color=mcrfpy.Color(35, 35, 45))
|
||||||
|
parent.children.append(self.frame)
|
||||||
self.frame = mcrfpy.Frame(pos=pos, size=(180, len(options) * 28 + 8))
|
|
||||||
self.frame.fill_color = mcrfpy.Color(35, 35, 45)
|
self.items = []
|
||||||
parent.children.append(self.frame)
|
for i, (label, value) in enumerate(options):
|
||||||
|
item = mcrfpy.Caption(text=label, pos=(12, 4 + i * 28))
|
||||||
self.items = []
|
item.fill_color = mcrfpy.Color(180, 180, 180)
|
||||||
for i, (label, value) in enumerate(options):
|
self.frame.children.append(item)
|
||||||
item = mcrfpy.Caption(pos=(12, 4 + i * 28), text=label)
|
self.items.append(item)
|
||||||
item.fill_color = mcrfpy.Color(180, 180, 180)
|
|
||||||
self.frame.children.append(item)
|
self._update_highlight()
|
||||||
self.items.append(item)
|
|
||||||
|
def _update_highlight(self):
|
||||||
self._update_highlight()
|
for i, item in enumerate(self.items):
|
||||||
|
if i == self.selected:
|
||||||
def _update_highlight(self):
|
item.fill_color = mcrfpy.Color(255, 220, 100)
|
||||||
for i, item in enumerate(self.items):
|
else:
|
||||||
if i == self.selected:
|
item.fill_color = mcrfpy.Color(180, 180, 180)
|
||||||
item.fill_color = mcrfpy.Color(255, 220, 100)
|
|
||||||
else:
|
def move_up(self):
|
||||||
item.fill_color = mcrfpy.Color(180, 180, 180)
|
self.selected = (self.selected - 1) % len(self.options)
|
||||||
|
self._update_highlight()
|
||||||
def move_up(self):
|
|
||||||
self.selected = (self.selected - 1) % len(self.options)
|
def move_down(self):
|
||||||
self._update_highlight()
|
self.selected = (self.selected + 1) % len(self.options)
|
||||||
|
self._update_highlight()
|
||||||
def move_down(self):
|
|
||||||
self.selected = (self.selected + 1) % len(self.options)
|
def confirm(self):
|
||||||
self._update_highlight()
|
_, value = self.options[self.selected]
|
||||||
|
self.on_select(value)
|
||||||
def confirm(self):
|
|
||||||
_, value = self.options[self.selected]
|
# Usage
|
||||||
self.on_select(value)
|
menu = VerticalMenu(root, (20, 20), [
|
||||||
|
("Continue", "continue"),
|
||||||
# Usage
|
("New Game", "new"),
|
||||||
menu = VerticalMenu(root, (20, 20), [
|
("Options", "options"),
|
||||||
("Continue", "continue"),
|
("Quit", "quit")
|
||||||
("New Game", "new"),
|
], on_select=handle_menu_choice)
|
||||||
("Options", "options"),
|
|
||||||
("Quit", "quit")
|
# Keyboard navigation via scene.on_key
|
||||||
], on_select=handle_menu_choice)
|
def handle_key(key, action):
|
||||||
|
if action != mcrfpy.InputState.PRESSED:
|
||||||
def handle_key(key, pressed):
|
return
|
||||||
if not pressed:
|
if key == mcrfpy.Key.UP:
|
||||||
return
|
menu.move_up()
|
||||||
if key == "Up":
|
elif key == mcrfpy.Key.DOWN:
|
||||||
menu.move_up()
|
menu.move_down()
|
||||||
elif key == "Down":
|
elif key == mcrfpy.Key.ENTER:
|
||||||
menu.move_down()
|
menu.confirm()
|
||||||
elif key == "Enter":
|
|
||||||
menu.confirm()
|
scene.on_key = handle_key
|
||||||
|
```
|
||||||
mcrfpy.keypressScene(handle_key)
|
|
||||||
```
|
---
|
||||||
|
|
||||||
---
|
## Modal Dialog
|
||||||
|
|
||||||
## Modal Dialog
|
A dialog that captures all input until dismissed.
|
||||||
|
|
||||||
A dialog that captures all input until dismissed.
|
```python
|
||||||
|
class ModalDialog:
|
||||||
```python
|
def __init__(self, scene, message, on_dismiss=None):
|
||||||
class ModalDialog:
|
self.scene = scene
|
||||||
def __init__(self, message, on_dismiss=None):
|
self.on_dismiss = on_dismiss
|
||||||
self.on_dismiss = on_dismiss
|
ui = scene.children
|
||||||
self.ui = mcrfpy.sceneUI(mcrfpy.currentScene())
|
|
||||||
|
# Semi-transparent backdrop
|
||||||
# Semi-transparent backdrop
|
self.backdrop = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
|
||||||
self.backdrop = mcrfpy.Frame(pos=(0, 0), size=(1024, 768))
|
fill_color=mcrfpy.Color(0, 0, 0, 160))
|
||||||
self.backdrop.fill_color = mcrfpy.Color(0, 0, 0, 160)
|
self.backdrop.z_index = 900
|
||||||
self.backdrop.z_index = 900
|
self.backdrop.on_click = lambda pos, btn, action: None # Block clicks
|
||||||
self.backdrop.on_click = lambda x, y, b: None # Block clicks
|
ui.append(self.backdrop)
|
||||||
self.ui.append(self.backdrop)
|
|
||||||
|
# Dialog box
|
||||||
# Dialog box
|
self.dialog = mcrfpy.Frame(pos=(312, 284), size=(400, 200),
|
||||||
self.dialog = mcrfpy.Frame(pos=(312, 284), size=(400, 200))
|
fill_color=mcrfpy.Color(50, 50, 65))
|
||||||
self.dialog.fill_color = mcrfpy.Color(50, 50, 65)
|
self.dialog.outline_color = mcrfpy.Color(120, 120, 150)
|
||||||
self.dialog.outline_color = mcrfpy.Color(120, 120, 150)
|
self.dialog.outline = 2
|
||||||
self.dialog.outline = 2
|
self.dialog.z_index = 901
|
||||||
self.dialog.z_index = 901
|
ui.append(self.dialog)
|
||||||
self.ui.append(self.dialog)
|
|
||||||
|
# Message
|
||||||
# Message
|
msg = mcrfpy.Caption(text=message, pos=(20, 20))
|
||||||
msg = mcrfpy.Caption(pos=(20, 20), text=message)
|
msg.fill_color = mcrfpy.Color(220, 220, 220)
|
||||||
msg.fill_color = mcrfpy.Color(220, 220, 220)
|
self.dialog.children.append(msg)
|
||||||
self.dialog.children.append(msg)
|
|
||||||
|
# OK button
|
||||||
# OK button
|
ok_btn = mcrfpy.Frame(pos=(150, 140), size=(100, 36),
|
||||||
ok_btn = mcrfpy.Frame(pos=(150, 140), size=(100, 36))
|
fill_color=mcrfpy.Color(70, 100, 70))
|
||||||
ok_btn.fill_color = mcrfpy.Color(70, 100, 70)
|
ok_btn.outline = 1
|
||||||
ok_btn.outline = 1
|
ok_btn.outline_color = mcrfpy.Color(100, 150, 100)
|
||||||
ok_btn.outline_color = mcrfpy.Color(100, 150, 100)
|
ok_btn.on_click = lambda pos, btn, action: self.close()
|
||||||
ok_btn.on_click = lambda x, y, b: self.close()
|
self.dialog.children.append(ok_btn)
|
||||||
self.dialog.children.append(ok_btn)
|
|
||||||
|
ok_label = mcrfpy.Caption(text="OK", pos=(35, 8))
|
||||||
ok_label = mcrfpy.Caption(pos=(35, 8), text="OK")
|
ok_label.fill_color = mcrfpy.Color(220, 255, 220)
|
||||||
ok_label.fill_color = mcrfpy.Color(220, 255, 220)
|
ok_btn.children.append(ok_label)
|
||||||
ok_btn.children.append(ok_label)
|
|
||||||
|
# Capture keyboard
|
||||||
# Capture keyboard
|
self._prev_handler = scene.on_key
|
||||||
self._prev_handler = None
|
|
||||||
self._install_key_handler()
|
def modal_keys(key, action):
|
||||||
|
if action == mcrfpy.InputState.PRESSED:
|
||||||
def _install_key_handler(self):
|
if key == mcrfpy.Key.ENTER or key == mcrfpy.Key.ESCAPE:
|
||||||
def modal_keys(key, pressed):
|
self.close()
|
||||||
if pressed and key in ("Enter", "Escape"):
|
scene.on_key = modal_keys
|
||||||
self.close()
|
|
||||||
mcrfpy.keypressScene(modal_keys)
|
def close(self):
|
||||||
|
ui = self.scene.children
|
||||||
def close(self):
|
ui.remove(self.backdrop)
|
||||||
self.ui.remove(self.backdrop)
|
ui.remove(self.dialog)
|
||||||
self.ui.remove(self.dialog)
|
if self._prev_handler:
|
||||||
if self.on_dismiss:
|
self.scene.on_key = self._prev_handler
|
||||||
self.on_dismiss()
|
if self.on_dismiss:
|
||||||
|
self.on_dismiss()
|
||||||
# Usage
|
|
||||||
def show_message(text):
|
# Usage
|
||||||
ModalDialog(text)
|
dialog = ModalDialog(scene, "Game saved successfully!")
|
||||||
|
```
|
||||||
show_message("Game saved successfully!")
|
|
||||||
```
|
---
|
||||||
|
|
||||||
---
|
## Hotbar / Quick Slots
|
||||||
|
|
||||||
## Hotbar / Quick Slots
|
Number keys (1-9) mapped to inventory slots.
|
||||||
|
|
||||||
Number keys (1-9) mapped to inventory slots.
|
```python
|
||||||
|
class Hotbar:
|
||||||
```python
|
def __init__(self, parent, pos, slot_count=9):
|
||||||
class Hotbar:
|
self.slots = []
|
||||||
def __init__(self, parent, pos, slot_count=9):
|
self.items = [None] * slot_count
|
||||||
self.slots = []
|
self.selected = 0
|
||||||
self.items = [None] * slot_count # Item data per slot
|
|
||||||
self.selected = 0
|
self.frame = mcrfpy.Frame(pos=pos,
|
||||||
|
size=(slot_count * 36 + 8, 44),
|
||||||
self.frame = mcrfpy.Frame(pos=pos, size=(slot_count * 36 + 8, 44))
|
fill_color=mcrfpy.Color(30, 30, 40, 200))
|
||||||
self.frame.fill_color = mcrfpy.Color(30, 30, 40, 200)
|
parent.children.append(self.frame)
|
||||||
parent.children.append(self.frame)
|
|
||||||
|
for i in range(slot_count):
|
||||||
for i in range(slot_count):
|
slot = mcrfpy.Frame(pos=(4 + i * 36, 4), size=(32, 32),
|
||||||
slot = mcrfpy.Frame(pos=(4 + i * 36, 4), size=(32, 32))
|
fill_color=mcrfpy.Color(50, 50, 60))
|
||||||
slot.fill_color = mcrfpy.Color(50, 50, 60)
|
slot.outline = 1
|
||||||
slot.outline = 1
|
slot.outline_color = mcrfpy.Color(80, 80, 100)
|
||||||
slot.outline_color = mcrfpy.Color(80, 80, 100)
|
self.frame.children.append(slot)
|
||||||
self.frame.children.append(slot)
|
self.slots.append(slot)
|
||||||
self.slots.append(slot)
|
|
||||||
|
num = mcrfpy.Caption(text=str((i + 1) % 10), pos=(2, 2))
|
||||||
# Slot number label
|
num.fill_color = mcrfpy.Color(100, 100, 120)
|
||||||
num = mcrfpy.Caption(pos=(2, 2), text=str((i + 1) % 10))
|
slot.children.append(num)
|
||||||
num.fill_color = mcrfpy.Color(100, 100, 120)
|
|
||||||
slot.children.append(num)
|
self._update_selection()
|
||||||
|
|
||||||
self._update_selection()
|
def _update_selection(self):
|
||||||
|
for i, slot in enumerate(self.slots):
|
||||||
def _update_selection(self):
|
if i == self.selected:
|
||||||
for i, slot in enumerate(self.slots):
|
slot.outline_color = mcrfpy.Color(200, 180, 80)
|
||||||
if i == self.selected:
|
slot.outline = 2
|
||||||
slot.outline_color = mcrfpy.Color(200, 180, 80)
|
else:
|
||||||
slot.outline = 2
|
slot.outline_color = mcrfpy.Color(80, 80, 100)
|
||||||
else:
|
slot.outline = 1
|
||||||
slot.outline_color = mcrfpy.Color(80, 80, 100)
|
|
||||||
slot.outline = 1
|
def select(self, index):
|
||||||
|
if 0 <= index < len(self.slots):
|
||||||
def select(self, index):
|
self.selected = index
|
||||||
if 0 <= index < len(self.slots):
|
self._update_selection()
|
||||||
self.selected = index
|
|
||||||
self._update_selection()
|
# Usage
|
||||||
|
hotbar = Hotbar(root, (200, 700))
|
||||||
def use_selected(self):
|
|
||||||
item = self.items[self.selected]
|
# Key mapping for number keys
|
||||||
if item:
|
num_keys = {
|
||||||
item.use()
|
mcrfpy.Key.NUM_1: 0, mcrfpy.Key.NUM_2: 1, mcrfpy.Key.NUM_3: 2,
|
||||||
|
mcrfpy.Key.NUM_4: 3, mcrfpy.Key.NUM_5: 4, mcrfpy.Key.NUM_6: 5,
|
||||||
def set_item(self, index, item):
|
mcrfpy.Key.NUM_7: 6, mcrfpy.Key.NUM_8: 7, mcrfpy.Key.NUM_9: 8,
|
||||||
self.items[index] = item
|
}
|
||||||
# Update slot visual (add sprite, etc.)
|
|
||||||
|
def handle_key(key, action):
|
||||||
# Usage
|
if action != mcrfpy.InputState.PRESSED:
|
||||||
hotbar = Hotbar(root, (200, 700))
|
return
|
||||||
|
if key in num_keys:
|
||||||
def handle_key(key, pressed):
|
hotbar.select(num_keys[key])
|
||||||
if not pressed:
|
|
||||||
return
|
scene.on_key = handle_key
|
||||||
# Number keys select slots
|
```
|
||||||
if key.startswith("Num") and len(key) == 4:
|
|
||||||
num = int(key[3])
|
---
|
||||||
index = (num - 1) if num > 0 else 9
|
|
||||||
hotbar.select(index)
|
## Draggable Window
|
||||||
elif key == "E":
|
|
||||||
hotbar.use_selected()
|
A frame that can be dragged by its title bar.
|
||||||
|
|
||||||
mcrfpy.keypressScene(handle_key)
|
```python
|
||||||
```
|
class DraggableWindow:
|
||||||
|
def __init__(self, parent, pos, size, title):
|
||||||
---
|
self.dragging = False
|
||||||
|
self.drag_offset = (0, 0)
|
||||||
## Draggable Window
|
|
||||||
|
self.frame = mcrfpy.Frame(pos=pos, size=size,
|
||||||
A frame that can be dragged by its title bar.
|
fill_color=mcrfpy.Color(45, 45, 55))
|
||||||
|
self.frame.outline = 1
|
||||||
```python
|
self.frame.outline_color = mcrfpy.Color(100, 100, 120)
|
||||||
class DraggableWindow:
|
parent.children.append(self.frame)
|
||||||
def __init__(self, parent, pos, size, title):
|
|
||||||
self.dragging = False
|
# Title bar
|
||||||
self.drag_offset = (0, 0)
|
self.title_bar = mcrfpy.Frame(pos=(0, 0), size=(size[0], 24),
|
||||||
|
fill_color=mcrfpy.Color(60, 60, 80))
|
||||||
self.frame = mcrfpy.Frame(pos=pos, size=size)
|
self.frame.children.append(self.title_bar)
|
||||||
self.frame.fill_color = mcrfpy.Color(45, 45, 55)
|
|
||||||
self.frame.outline = 1
|
title_label = mcrfpy.Caption(text=title, pos=(8, 4))
|
||||||
self.frame.outline_color = mcrfpy.Color(100, 100, 120)
|
title_label.fill_color = mcrfpy.Color(200, 200, 220)
|
||||||
parent.children.append(self.frame)
|
self.title_bar.children.append(title_label)
|
||||||
|
|
||||||
# Title bar
|
self.content_y = 28
|
||||||
self.title_bar = mcrfpy.Frame(pos=(0, 0), size=(size[0], 24))
|
|
||||||
self.title_bar.fill_color = mcrfpy.Color(60, 60, 80)
|
# Drag handling
|
||||||
self.frame.children.append(self.title_bar)
|
# on_click: (pos: Vector, button: MouseButton, action: InputState)
|
||||||
|
def start_drag(pos, button, action):
|
||||||
title_label = mcrfpy.Caption(pos=(8, 4), text=title)
|
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
|
||||||
title_label.fill_color = mcrfpy.Color(200, 200, 220)
|
self.dragging = True
|
||||||
self.title_bar.children.append(title_label)
|
self.drag_offset = (pos.x - self.frame.x, pos.y - self.frame.y)
|
||||||
|
|
||||||
# Content area reference
|
# on_move: (pos: Vector)
|
||||||
self.content_y = 28
|
def on_move(pos):
|
||||||
|
if self.dragging:
|
||||||
# Drag handling
|
self.frame.x = pos.x - self.drag_offset[0]
|
||||||
def start_drag(x, y, btn):
|
self.frame.y = pos.y - self.drag_offset[1]
|
||||||
if btn == 0: # Left click
|
|
||||||
self.dragging = True
|
# on_exit: (pos: Vector)
|
||||||
frame_pos = self.frame.pos
|
def stop_drag(pos):
|
||||||
self.drag_offset = (x - frame_pos[0], y - frame_pos[1])
|
self.dragging = False
|
||||||
|
|
||||||
def on_move(x, y):
|
self.title_bar.on_click = start_drag
|
||||||
if self.dragging:
|
self.title_bar.on_move = on_move
|
||||||
new_x = x - self.drag_offset[0]
|
self.title_bar.on_exit = stop_drag
|
||||||
new_y = y - self.drag_offset[1]
|
|
||||||
self.frame.pos = (new_x, new_y)
|
# Usage
|
||||||
|
window = DraggableWindow(root, (100, 100), (250, 300), "Inventory")
|
||||||
def stop_drag():
|
|
||||||
self.dragging = False
|
# Add content to window.frame.children at y >= window.content_y
|
||||||
|
item_list = mcrfpy.Caption(text="Items here...", pos=(10, window.content_y + 10))
|
||||||
self.title_bar.on_click = start_drag
|
window.frame.children.append(item_list)
|
||||||
self.title_bar.on_move = on_move
|
```
|
||||||
self.title_bar.on_exit = stop_drag
|
|
||||||
|
---
|
||||||
# Usage
|
|
||||||
window = DraggableWindow(root, (100, 100), (250, 300), "Inventory")
|
## Related Pages
|
||||||
|
|
||||||
# Add content to window.frame.children at y >= window.content_y
|
- [[Input-and-Events]] - Event handler API reference
|
||||||
item_list = mcrfpy.Caption(pos=(10, window.content_y + 10), text="Items here...")
|
- [[Grid-Interaction-Patterns]] - Patterns for grid-based gameplay
|
||||||
window.frame.children.append(item_list)
|
- [[Animation-System]] - Animating widget transitions
|
||||||
```
|
|
||||||
|
---
|
||||||
---
|
|
||||||
|
*Last updated: 2026-02-07*
|
||||||
## Related Pages
|
|
||||||
|
|
||||||
- [[Input-and-Events]] - Event handler API reference
|
|
||||||
- [[Grid-Interaction-Patterns]] - Patterns for grid-based gameplay
|
|
||||||
- [[Animation-System]] - Animating widget transitions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Last updated: 2025-11-29*
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue