PHASE 17: UI Helpers - Button, panel, progress bar, tooltip, notification, checkbox creators
This commit is contained in:
@@ -64,6 +64,7 @@
|
|||||||
|
|
||||||
<!-- UI Theme -->
|
<!-- UI Theme -->
|
||||||
<script src="src/ui/UITheme.js"></script>
|
<script src="src/ui/UITheme.js"></script>
|
||||||
|
<script src="src/ui/UIHelpers.js"></script>
|
||||||
|
|
||||||
<!-- Utilities -->
|
<!-- Utilities -->
|
||||||
<script src="src/utils/PerlinNoise.js"></script>
|
<script src="src/utils/PerlinNoise.js"></script>
|
||||||
|
|||||||
312
src/ui/UIHelpers.js
Normal file
312
src/ui/UIHelpers.js
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
/**
|
||||||
|
* UI HELPERS
|
||||||
|
* Quick utility functions for creating themed UI elements
|
||||||
|
* Uses UITheme for consistent styling
|
||||||
|
*/
|
||||||
|
|
||||||
|
class UIHelpers {
|
||||||
|
/**
|
||||||
|
* Create themed button
|
||||||
|
*/
|
||||||
|
static createButton(scene, x, y, text, callback, buttonType = 'primary') {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
const btnStyle = theme.buttons[buttonType] || theme.buttons.primary;
|
||||||
|
|
||||||
|
// Button background
|
||||||
|
const width = Math.max(120, text.length * 10 + 40);
|
||||||
|
const height = 40;
|
||||||
|
|
||||||
|
const button = scene.add.container(x, y);
|
||||||
|
|
||||||
|
// Background rectangle
|
||||||
|
const bg = scene.add.rectangle(0, 0, width, height,
|
||||||
|
parseInt(btnStyle.background.replace('#', '0x')), 1);
|
||||||
|
bg.setStrokeStyle(2, parseInt(btnStyle.border.replace('#', '0x')));
|
||||||
|
|
||||||
|
// Text
|
||||||
|
const btnText = scene.add.text(0, 0, text, {
|
||||||
|
fontFamily: theme.fonts.primary,
|
||||||
|
fontSize: theme.fontSizes.medium,
|
||||||
|
color: btnStyle.text
|
||||||
|
});
|
||||||
|
btnText.setOrigin(0.5);
|
||||||
|
|
||||||
|
button.add([bg, btnText]);
|
||||||
|
|
||||||
|
// Make interactive
|
||||||
|
bg.setInteractive({ useHandCursor: true });
|
||||||
|
|
||||||
|
// Hover effect
|
||||||
|
bg.on('pointerover', () => {
|
||||||
|
bg.setFillStyle(parseInt(btnStyle.backgroundHover.replace('#', '0x')));
|
||||||
|
});
|
||||||
|
|
||||||
|
bg.on('pointerout', () => {
|
||||||
|
bg.setFillStyle(parseInt(btnStyle.background.replace('#', '0x')));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click
|
||||||
|
if (callback) {
|
||||||
|
bg.on('pointerdown', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create themed panel
|
||||||
|
*/
|
||||||
|
static createPanel(scene, x, y, width, height, panelType = 'wooden') {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
return theme.createPanel(scene, x, y, width, height, panelType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create themed text
|
||||||
|
*/
|
||||||
|
static createText(scene, x, y, text, style = 'normal') {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
const textStyle = theme.applyTextStyle(null, style);
|
||||||
|
|
||||||
|
const textObj = scene.add.text(x, y, text, textStyle);
|
||||||
|
return textObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create header text
|
||||||
|
*/
|
||||||
|
static createHeader(scene, x, y, text) {
|
||||||
|
return this.createText(scene, x, y, text, 'header');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create progress bar
|
||||||
|
*/
|
||||||
|
static createProgressBar(scene, x, y, width, height, progress = 0.5) {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
|
||||||
|
const container = scene.add.container(x, y);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
const bg = scene.add.rectangle(0, 0, width, height,
|
||||||
|
theme.getColor('backgrounds.dark'), 1);
|
||||||
|
bg.setStrokeStyle(2, theme.getColor('secondary.steel'));
|
||||||
|
bg.setOrigin(0, 0.5);
|
||||||
|
|
||||||
|
// Fill
|
||||||
|
const fill = scene.add.rectangle(2, 0, (width - 4) * progress, height - 4,
|
||||||
|
theme.getColor('states.success'), 1);
|
||||||
|
fill.setOrigin(0, 0.5);
|
||||||
|
|
||||||
|
container.add([bg, fill]);
|
||||||
|
|
||||||
|
// Update method
|
||||||
|
container.setProgress = function (value) {
|
||||||
|
fill.width = Math.max(0, Math.min(width - 4, (width - 4) * value));
|
||||||
|
};
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create tooltip
|
||||||
|
*/
|
||||||
|
static createTooltip(scene, x, y, text) {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
|
||||||
|
const tooltip = scene.add.container(x, y);
|
||||||
|
tooltip.setDepth(100000);
|
||||||
|
|
||||||
|
// Measure text
|
||||||
|
const tempText = scene.add.text(0, 0, text, {
|
||||||
|
fontFamily: theme.fonts.primary,
|
||||||
|
fontSize: theme.fontSizes.small
|
||||||
|
});
|
||||||
|
const textWidth = tempText.width;
|
||||||
|
const textHeight = tempText.height;
|
||||||
|
tempText.destroy();
|
||||||
|
|
||||||
|
// Background
|
||||||
|
const padding = 8;
|
||||||
|
const bg = scene.add.rectangle(0, 0, textWidth + padding * 2, textHeight + padding * 2,
|
||||||
|
theme.getColor('backgrounds.overlay'));
|
||||||
|
bg.setStrokeStyle(1, theme.getColor('primary.lightBrown'));
|
||||||
|
|
||||||
|
// Text
|
||||||
|
const tooltipText = scene.add.text(0, 0, text, {
|
||||||
|
fontFamily: theme.fonts.primary,
|
||||||
|
fontSize: theme.fontSizes.small,
|
||||||
|
color: theme.colors.text.primary
|
||||||
|
});
|
||||||
|
tooltipText.setOrigin(0.5);
|
||||||
|
|
||||||
|
tooltip.add([bg, tooltipText]);
|
||||||
|
tooltip.setAlpha(0);
|
||||||
|
|
||||||
|
// Show/hide methods
|
||||||
|
tooltip.show = function () {
|
||||||
|
this.setAlpha(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
tooltip.hide = function () {
|
||||||
|
this.setAlpha(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create notification popup
|
||||||
|
*/
|
||||||
|
static createNotification(scene, text, duration = 2000, type = 'info') {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
|
||||||
|
const width = scene.cameras.main.width;
|
||||||
|
const height = scene.cameras.main.height;
|
||||||
|
|
||||||
|
const notification = scene.add.container(width / 2, height - 100);
|
||||||
|
notification.setScrollFactor(0);
|
||||||
|
notification.setDepth(100000);
|
||||||
|
|
||||||
|
// Background color based on type
|
||||||
|
let bgColor = theme.getColor('states.' + type);
|
||||||
|
if (!bgColor) bgColor = theme.getColor('backgrounds.medium');
|
||||||
|
|
||||||
|
// Background
|
||||||
|
const bg = scene.add.rectangle(0, 0, 300, 60, bgColor, 0.95);
|
||||||
|
bg.setStrokeStyle(2, theme.getColor('primary.darkBrown'));
|
||||||
|
|
||||||
|
// Text
|
||||||
|
const notifText = scene.add.text(0, 0, text, {
|
||||||
|
fontFamily: theme.fonts.primary,
|
||||||
|
fontSize: theme.fontSizes.medium,
|
||||||
|
color: theme.colors.text.primary,
|
||||||
|
align: 'center',
|
||||||
|
wordWrap: { width: 280 }
|
||||||
|
});
|
||||||
|
notifText.setOrigin(0.5);
|
||||||
|
|
||||||
|
notification.add([bg, notifText]);
|
||||||
|
|
||||||
|
// Animate in
|
||||||
|
notification.setAlpha(0);
|
||||||
|
notification.y = height - 50;
|
||||||
|
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: notification,
|
||||||
|
alpha: 1,
|
||||||
|
y: height - 100,
|
||||||
|
duration: 300,
|
||||||
|
ease: 'Back.easeOut'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Animate out
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: notification,
|
||||||
|
alpha: 0,
|
||||||
|
y: height - 50,
|
||||||
|
duration: 300,
|
||||||
|
delay: duration,
|
||||||
|
onComplete: () => {
|
||||||
|
notification.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create icon button (emoji/icon only)
|
||||||
|
*/
|
||||||
|
static createIconButton(scene, x, y, icon, callback, size = 40) {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
|
||||||
|
const button = scene.add.container(x, y);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
const bg = scene.add.circle(0, 0, size / 2,
|
||||||
|
theme.getColor('primary.mediumBrown'), 1);
|
||||||
|
bg.setStrokeStyle(2, theme.getColor('primary.darkBrown'));
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
const iconText = scene.add.text(0, 0, icon, {
|
||||||
|
fontSize: (size * 0.6) + 'px'
|
||||||
|
});
|
||||||
|
iconText.setOrigin(0.5);
|
||||||
|
|
||||||
|
button.add([bg, iconText]);
|
||||||
|
|
||||||
|
// Interactive
|
||||||
|
bg.setInteractive({ useHandCursor: true });
|
||||||
|
|
||||||
|
bg.on('pointerover', () => {
|
||||||
|
bg.setFillStyle(theme.getColor('primary.lightBrown'));
|
||||||
|
});
|
||||||
|
|
||||||
|
bg.on('pointerout', () => {
|
||||||
|
bg.setFillStyle(theme.getColor('primary.mediumBrown'));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
bg.on('pointerdown', callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create checkbox
|
||||||
|
*/
|
||||||
|
static createCheckbox(scene, x, y, label, isChecked = false, callback) {
|
||||||
|
const theme = window.UITheme;
|
||||||
|
|
||||||
|
const checkbox = scene.add.container(x, y);
|
||||||
|
|
||||||
|
// Box
|
||||||
|
const size = 20;
|
||||||
|
const box = scene.add.rectangle(0, 0, size, size,
|
||||||
|
theme.getColor('backgrounds.light'), 1);
|
||||||
|
box.setStrokeStyle(2, theme.getColor('primary.darkBrown'));
|
||||||
|
box.setOrigin(0, 0.5);
|
||||||
|
|
||||||
|
// Checkmark
|
||||||
|
const check = scene.add.text(size / 2, 0, '✓', {
|
||||||
|
fontSize: '18px',
|
||||||
|
color: theme.colors.accent.forestGreen
|
||||||
|
});
|
||||||
|
check.setOrigin(0.5);
|
||||||
|
check.setVisible(isChecked);
|
||||||
|
|
||||||
|
// Label
|
||||||
|
const labelText = scene.add.text(size + 10, 0, label, {
|
||||||
|
fontFamily: theme.fonts.primary,
|
||||||
|
fontSize: theme.fontSizes.normal,
|
||||||
|
color: theme.colors.text.primary
|
||||||
|
});
|
||||||
|
labelText.setOrigin(0, 0.5);
|
||||||
|
|
||||||
|
checkbox.add([box, check, labelText]);
|
||||||
|
|
||||||
|
// State
|
||||||
|
checkbox.checked = isChecked;
|
||||||
|
|
||||||
|
// Interactive
|
||||||
|
box.setInteractive({ useHandCursor: true });
|
||||||
|
|
||||||
|
box.on('pointerdown', () => {
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
|
check.setVisible(checkbox.checked);
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(checkbox.checked);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return checkbox;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make globally available
|
||||||
|
window.UIHelpers = UIHelpers;
|
||||||
|
|
||||||
|
console.log('🎨 UI Helpers loaded');
|
||||||
Reference in New Issue
Block a user