Skip to content

Instantly share code, notes, and snippets.

@williammustaffa
Last active May 8, 2022 13:21
Show Gist options
  • Save williammustaffa/9e22bb1bdc23e6fb9823addf3649a32a to your computer and use it in GitHub Desktop.
Save williammustaffa/9e22bb1bdc23e6fb9823addf3649a32a to your computer and use it in GitHub Desktop.
Simple CSS-Like Game Maker Studio 2 UI
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
varying vec3 v_vPosition;
uniform vec4 u_bounds;
void main() {
vec4 color = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
// calculate alpha
color.a *= float(
v_vPosition.x >= u_bounds[0] &&
v_vPosition.y >= u_bounds[1] &&
v_vPosition.x < u_bounds[2] &&
v_vPosition.y < u_bounds[3]
);
gl_FragColor = color;
}
attribute vec3 in_Position;
attribute vec4 in_Colour;
attribute vec2 in_TextureCoord;
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
varying vec3 v_vPosition;
void main() {
v_vPosition = in_Position;
gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position.x, in_Position.y, in_Position.z, 1.0);
v_vColour = in_Colour;
v_vTexcoord = in_TextureCoord;
}
function UIElement() constructor {
// General
debug = false;
precise = true;
// Rendering
overflow = "visible";
overflow_x = undefined;
overflow_y = undefined;
// Text
opacity = 1;
color = make_color_rgb(255, 211, 163);
color_hover = make_color_rgb(191, 38, 81);
font_family = fnt_default;
hoverable = false;
// Position
orientation = "horizontal"; // Accepts "horizontal" and "vertical"
position = "relative";
left = 0;
top = 0;
vertical_align = "top"; // Accepts "top"; "middle"; "bottom"
horizontal_align = "left"; // Accepts "left"; "middle"; "right"
// Dimensions
height = "auto"; // Accepts "auto" and numbers
width = "auto"; // Accepts "auto" and numbers
max_width = display_get_gui_width();
max_height = display_get_gui_height();
min_width = 0;
min_height = 0;
// Padding
padding = 0;
padding_bottom = undefined;
padding_top = undefined;
padding_left = undefined;
padding_right = undefined;
// Margin
margin = 0;
margin_bottom = undefined;
margin_top = undefined;
margin_left = undefined;
margin_right = undefined;
// Background
background = undefined;
background_opacity = 1;
background_surface = undefined;
// Listeners
onclick = undefined;
onmousein = undefined;
onmouseout = undefined;
onscroll = undefined;
// Scroll
scrollbar_width = 10;
scroll_speed = 40;
scrollbar_background = c_dkgray;
scrollbar_color = c_ltgray;
// Control variables
_nodes = [];
_text = undefined;
_parent = undefined;
_computed = false;
_top = 0;
_left = 0;
_offset_left = 0;
_offset_top = 0;
_scroll_offset_top = 0;
_scroll_offset_left = 0;
_margin_left = 0;
_margin_right = 0;
_margin_top = 0;
_margin_bottom = 0;
_padding_left = 0;
_padding_right = 0;
_padding_top = 0;
_padding_bottom = 0;
_horizontal_spacing = 0;
_vertical_spacing = 0;
_content_width = 0;
_content_height = 0;
_width = 0;
_height = 0;
_max_width = 0;
_max_height = 0;
_top_stacked = 0;
_left_stacked = 0;
_highest_node_width = 0;
_highest_node_height = 0;
_color = 0;
_separation = 0;
_mousein = false;
_shader_enabled = false;
_overflow_x = undefined;
_overflow_y = undefined;
_overflow_hide_x = undefined;
_overflow_hide_y = undefined;
_scrollbar_horizontal_enabled = false;
_scrollbar_horizontal_pointer_position = 0;
_scrollbar_horizontal_pointer_height = 0;
_scrollbar_vertical_enabled = false;
_scrollbar_vertical_pointer_position = 0;
_scrollbar_vertical_pointer_height = 0;
static clear = function () {
_nodes = [];
}
static create = function (__text) {
var __node = new UIElement();
__node._parent = self;
__node.debug = debug;
if (!is_undefined(__text)) {
__node._text = __text;
}
array_push(_nodes, __node);
return __node;
}
static update = function () {
_compute_reset();
_compute_font();
_compute_spacing();
_compute_text();
_compute_children();
_compute_dimensions();
_compute_position();
_compute_alignment();
_compute_events();
_compute_scrollbar();
_computed = true;
}
static draw = function () {
// Still computing data, so do nothing
if (!_computed) return;
// Render
_render_prepare();
_render_background();
_render_text();
_render_children();
_render_scrollbars();
_render_debug();
_render_finish();
}
static getLeftPosition = function () {
var __left = _left;
if (!is_undefined(_parent) and position == "relative") {
__left += _parent._offset_left;
__left += _parent._scroll_offset_left;
}
return __left;
}
static getTopPosition = function () {
var __top = _top;
if (!is_undefined(_parent) and position == "relative") {
__top += _parent._offset_top;
__top += _parent._scroll_offset_top;
}
return __top;
}
static _render_prepare = function () {
draw_set_font(font_family);
if (_overflow_hide_x || _overflow_hide_y) {
// Enables shader and set flag to true indicating it should be disabled later
shader_set(sh_element_clip);
_shader_enabled = true;
// Get uniforms from shader
var __u_bounds = shader_get_uniform(sh_element_clip, "u_bounds");
var __left = getLeftPosition();
var __top = getTopPosition();
shader_set_uniform_f(
__u_bounds,
__left + _margin_left,
__top + _margin_top,
__left + _width - _margin_right,
__top + _height - _margin_bottom,
);
}
}
static _render_background = function () {
var __left = getLeftPosition();
var __top = getTopPosition();
if (!is_undefined(background_surface) and surface_exists(background_surface)) {
draw_surface_part_ext(background_surface, 0, 0, _width, _height, __left, __top, 1, 1, c_white, 1);
}
if (!is_undefined(background)) {
draw_set_alpha(background_opacity);
draw_rectangle_color(
__left + _margin_left,
__top + _margin_top,
__left + _width - _margin_right,
__top + _height - _margin_bottom,
background,
background,
background,
background,
0
);
draw_set_alpha(1);
}
}
static _render_text = function () {
if (!is_undefined(_text)) {
draw_text_ext_color(
_offset_left,
_offset_top,
_text,
_separation,
_width - _horizontal_spacing,
_color,
_color,
_color,
_color,
opacity
);
}
}
static _render_children = function () {
// Run update on child nodes,
// This will run compute for all childrens
for (var __index = 0; __index < array_length(_nodes); __index++) {
var __node = _nodes[__index];
__node.draw();
}
}
static _render_scrollbar = function (__orientation) {
var __left = getLeftPosition();
var __top = getTopPosition();
// Get positions
var __sb_left, __sb_top, __sb_width, __sb_height,
__sbp_left, __sbp_top, __sbp_width, __sbp_height;
if (__orientation == "horizontal") {
__sb_left = __left;
__sb_top = __top + _height - scrollbar_width;
__sb_width = _width;
__sb_height = scrollbar_width;
__sbp_left = __left + _scrollbar_horizontal_pointer_position;
__sbp_top = __top + _height - scrollbar_width;
__sbp_width = _scrollbar_horizontal_pointer_height;
__sbp_height = scrollbar_width;
}
if (__orientation == "vertical") {
__sb_left = __left + _width - scrollbar_width;
__sb_top = __top;
__sb_width = scrollbar_width;
__sb_height = _height;
__sbp_left = __left + _width - scrollbar_width;
__sbp_top = __top + _scrollbar_vertical_pointer_position;
__sbp_width = scrollbar_width;
__sbp_height = _scrollbar_vertical_pointer_height;
}
draw_rectangle_color(
__sb_left,
__sb_top,
__sb_left + __sb_width,
__sb_top + __sb_height,
scrollbar_background,
scrollbar_background,
scrollbar_background,
scrollbar_background,
0
);
draw_rectangle_color(
__sbp_left,
__sbp_top,
__sbp_left + __sbp_width,
__sbp_top + __sbp_height,
scrollbar_color,
scrollbar_color,
scrollbar_color,
scrollbar_color,
0
);
}
static _render_scrollbars = function () {
if (_scrollbar_horizontal_enabled) {
_render_scrollbar("horizontal");
}
if (_scrollbar_vertical_enabled) {
_render_scrollbar("vertical");
}
}
static _render_finish = function () {
if (_shader_enabled) {
shader_reset();
}
}
static _render_debug = function () {
if (debug) {
var __left = getLeftPosition();
var __top = getTopPosition();
// Debug container space
draw_rectangle_color(
__left,
__top,
__left + _width,
__top + _height ,
c_red,
c_red,
c_red,
c_red,
1
);
// Debug margin
draw_rectangle_color(
__left + _margin_left ,
__top + _margin_top,
__left + _width - _margin_right ,
__top + _height - _margin_bottom,
c_green,
c_green,
c_green,
c_green,
1
);
// Debug padding
draw_rectangle_color(
__left + _margin_left + _padding_left,
__top + _margin_top + _padding_top,
__left + _width - _margin_right - _padding_right,
__top + _height - _margin_bottom - _padding_bottom,
c_yellow,
c_yellow,
c_yellow,
c_yellow,
1
);
// Debug content
draw_rectangle_color(
_offset_left,
_offset_top,
_offset_left + _content_width,
_offset_top + _content_height,
c_purple,
c_purple,
c_purple,
c_purple,
1
);
}
}
static _compute_font = function () {
draw_set_font(font_family);
_color = color;
_separation = string_height("dummy");
}
static _compute_reset = function () {
// Reset stacking
_left_stacked = 0;
_top_stacked = 0;
_highest_node_width = 0;
_highest_node_height = 0;
// Reset positioning
_left = left;
_top = top;
// Reset dimension
//_width = 0;
//_height = 0;
_content_width = 0;
_content_height = 0;
}
static _compute_spacing = function () {
// Compute margin
_margin_left = is_undefined(margin_left) ? margin : margin_left;
_margin_right = is_undefined(margin_right) ? margin : margin_right;
_margin_top = is_undefined(margin_top) ? margin : margin_top;
_margin_bottom = is_undefined(margin_bottom) ? margin : margin_bottom;
// Compute padding
_padding_left = is_undefined(padding_left) ? padding : padding_left ;
_padding_right = is_undefined(padding_right) ? padding : padding_right;
_padding_top = is_undefined(padding_top) ? padding : padding_top;
_padding_bottom = is_undefined(padding_bottom) ? padding : padding_bottom;
// Compute spacing
_horizontal_spacing = _margin_left + _padding_left + _margin_right + _padding_right;
_vertical_spacing = _margin_top + _padding_top + _margin_bottom + _padding_bottom;
// Compute overflow
_overflow_x = is_undefined(overflow_x) ? overflow : overflow_x;
_overflow_y = is_undefined(overflow_y) ? overflow : overflow_y;
_overflow_hide_x = _overflow_x == "hidden" or _overflow_x == "scroll";
_overflow_hide_y = _overflow_y == "hidden" or _overflow_y == "scroll";
// Compute max with
// If width or height is auto, then we rely on max_width and max_height
// Otherwise we set max height and max width to container width
_max_width = is_real(width) ? width : max_width;
_max_height = is_real(height) ? height : max_height;
// Get max width that should be the min value found as a limit
if (position == "relative" and !is_undefined(_parent) and _parent.width != "auto") {
_max_width = min(_parent._max_width - _parent._horizontal_spacing, _max_width);
}
}
static _compute_text = function () {
if (!is_undefined(_text)) {
var __text_width = string_width_ext(_text, _separation, _max_width - _horizontal_spacing);
var __text_height = string_height_ext(_text, _separation, _max_width - _horizontal_spacing);
_content_width += __text_width;
if (orientation == "horizontal") _left_stacked += __text_width;
_content_height += __text_height;
if (orientation == "vertical") _top_stacked += __text_height;
}
}
static _compute_children = function () {
for (var __index = 0; __index < array_length(_nodes); __index++) {
var __node = _nodes[__index];
__node.update();
}
}
static _compute_dimensions = function () {
// Compute node width
switch(width) {
case "max":
_width = _max_width;
break;
default:
if (is_real(width)) {
_width = width;
} else {
_width = _content_width + _horizontal_spacing;
};
}
switch(height) {
case "max":
_height = _max_height;
break;
default:
if (is_real(height)) {
_height = height;
} else {
// Auto
_height = _content_height + _vertical_spacing;
};
}
// Apply min width and min height
_width = max(_width, min_width);
_height = max(_height, min_height);
// Check if should enable scrollbar
_scrollbar_horizontal_enabled = _overflow_x == "scroll" and _content_width > _width;
_scrollbar_vertical_enabled = _overflow_y == "scroll" and _content_height > _height;
}
static _compute_position = function () {
if (position == "relative" and !is_undefined(_parent)) {
if (_parent.orientation == "horizontal") {
if (_parent._left_stacked + _width > _parent._max_width - _parent._horizontal_spacing) {
_parent._top_stacked += _parent._highest_node_height;
_parent._highest_node_height = 0;
_parent._left_stacked = 0;
}
_parent._highest_node_height = max(_parent._highest_node_height, _height);
// Update position relative to parent
_left += _parent._left_stacked;
_top += _parent._top_stacked;
// Increase width
_parent._left_stacked += _width;
}
if (_parent.orientation == "vertical") {
if (_parent._top_stacked + _height > _parent._max_height - _parent._vertical_spacing) {
_parent._left_stacked += _parent._highest_node_width;
_parent._top_stacked = 0;
_parent._highest_node_width = 0;
}
_parent._highest_node_width = max(_parent._highest_node_width, _width);
// Update position relative to parent
_left += _parent._left_stacked;
_top += _parent._top_stacked;
// Increase height
_parent._top_stacked += _height;
}
// Update parent content width
var __target_width = _parent._left_stacked
if (_parent.orientation == "vertical") {
__target_width += _parent._highest_node_width
};
if (__target_width > _parent._content_width) {
_parent._content_width = __target_width;
}
// Update parent content height
var __target_height = _parent._top_stacked;
if (_parent.orientation == "horizontal") {
__target_height += _parent._highest_node_height;
}
if (__target_height > _parent._content_height) {
_parent._content_height = __target_height;
}
}
}
static _compute_alignment = function () {
if (horizontal_align = "left") {
_offset_left = _left + _padding_left + _margin_left;
}
if (horizontal_align = "center") {
_offset_left = _left + _width / 2 - _content_width / 2;
}
if (horizontal_align = "right") {
_offset_left = _left + _width - _content_width - _padding_right - margin_right;
}
if (vertical_align = "top") {
_offset_top = _top + _padding_top + _margin_top;
}
if (vertical_align = "center") {
_offset_top = _top + _height / 2 - _content_height / 2;
}
if (vertical_align = "bottom") {
_offset_top = _top + _height - _content_height - _padding_bottom - _margin_bottom;
}
if (!is_undefined(_parent) and position = "relative") {
_offset_left += _parent._offset_left;
_offset_top += _parent._offset_top;
}
// This is not so performatic but it ensures that children position
// Will be updated each step. Negative point is that this method will run more times
// exponentially depending on the number of sub nodes
if (precise) {
for (var __index = 0; __index < array_length(_nodes); __index++) {
var __node = _nodes[__index];
__node._compute_alignment();
}
}
}
static _compute_events = function () {
var __left = getLeftPosition();
var __top = getTopPosition();
// Event handling
if (point_in_rectangle(
window_mouse_get_x(),
window_mouse_get_y(),
__left + _margin_left,
__top + _margin_top,
__left + _width - _margin_right,
__top + _height - _margin_bottom
)) {
// Execute onmousein event
if (_mousein == false) {
_mousein = true;
_execute_callback(onmousein);
}
// Execute onclick event
if (mouse_check_button_pressed(mb_left)) {
_execute_callback(onclick);
}
// Vertical scrolling
if (!keyboard_check(vk_shift) && _scrollbar_vertical_enabled) {
var __scroll_step = mouse_wheel_up() - mouse_wheel_down();
if (__scroll_step != 0) {
var __scroll_speed = scroll_speed;
var __target_scroll_offset_top = _scroll_offset_top + (__scroll_step * __scroll_speed);
_scroll_offset_top = clamp(__target_scroll_offset_top, _height - _content_height - _vertical_spacing, 0);
_execute_callback(onscroll);
}
}
// Horizontal scrolling
if (keyboard_check(vk_shift) && _scrollbar_horizontal_enabled) {
var __scroll_step = mouse_wheel_up() - mouse_wheel_down();
if (__scroll_step != 0) {
var __scroll_speed = scroll_speed;
var __target_scroll_offset_left = _scroll_offset_left + (__scroll_step * __scroll_speed);
_scroll_offset_left = clamp(__target_scroll_offset_left, _width - _content_width - _horizontal_spacing, 0);
_execute_callback(onscroll);
}
}
if (hoverable) {
_color = color_hover;
}
} else {
if (_mousein) {
_mousein = false;
_execute_callback(onmouseout);
}
}
}
static _compute_scrollbar = function () {
var __total_width = _width;
if (_scrollbar_vertical_enabled) {
var __total_height = _height - (_scrollbar_horizontal_enabled ? scrollbar_width : 0);
var __sections = (_content_height + _vertical_spacing) / __total_height;
// Compute position
_scrollbar_vertical_pointer_height = __total_height / __sections;
_scrollbar_vertical_pointer_position = abs(_scroll_offset_top / __sections);
}
if (_scrollbar_horizontal_enabled) {
var __total_width = _width - (_scrollbar_vertical_enabled ? scrollbar_width : 0);
var __sections = (_content_width + _horizontal_spacing) / __total_width;
// Compute position
_scrollbar_horizontal_pointer_height = __total_width / __sections;
_scrollbar_horizontal_pointer_position = abs(_scroll_offset_left / __sections);
}
}
static _execute_callback = function (__callback) {
if (!is_undefined(__callback)) {
if (typeof(__callback) == "method") __callback(self);
if (script_exists(__callback)) script_execute(__callback);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment