diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gui/Button.cpp | 71 | ||||
-rw-r--r-- | src/gui/Widget.cpp | 7 | ||||
-rw-r--r-- | src/main.cpp | 276 |
3 files changed, 354 insertions, 0 deletions
diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp new file mode 100644 index 0000000..b266639 --- /dev/null +++ b/src/gui/Button.cpp @@ -0,0 +1,71 @@ +#include "../../include/gui/Button.hpp" +#include <mglpp/graphics/Rectangle.hpp> +#include <mglpp/window/Window.hpp> +#include <mglpp/window/Event.hpp> +#include <mglpp/system/FloatRect.hpp> + +namespace gsr { + Button::Button(mgl::vec2f size) : size(size) { + + } + + void Button::on_event(mgl::Event &event, mgl::Window&) { + if(event.type == mgl::Event::MouseMoved) { + const bool inside = mgl::FloatRect(position, size).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y }); + if(mouse_inside && !inside) { + mouse_inside = false; + } else if(!mouse_inside && inside) { + mouse_inside = true; + } + } + } + + void Button::draw(mgl::Window &window) { + if(mouse_inside) { + // Background + /* + { + mgl::Rectangle rect(size); + rect.set_position(position); + rect.set_color(mgl::Color(20, 20, 20, 255)); + window.draw(rect); + } + */ + + const int border_size = 5; + const mgl::Color border_color(118, 185, 0); + + // Green line at top + { + mgl::Rectangle rect({ size.x, border_size }); + rect.set_position(position); + rect.set_color(border_color); + window.draw(rect); + } + + // Green line at bottom + { + mgl::Rectangle rect({ size.x, border_size }); + rect.set_position(position + mgl::vec2f(0.0f, size.y - border_size)); + rect.set_color(border_color); + window.draw(rect); + } + + // Green line at left + { + mgl::Rectangle rect({ border_size, size.y }); + rect.set_position(position); + rect.set_color(border_color); + window.draw(rect); + } + + // Green line at right + { + mgl::Rectangle rect({ border_size, size.y }); + rect.set_position(position + mgl::vec2f(size.x - border_size, 0.0f)); + rect.set_color(border_color); + window.draw(rect); + } + } + } +}
\ No newline at end of file diff --git a/src/gui/Widget.cpp b/src/gui/Widget.cpp new file mode 100644 index 0000000..c30c1d1 --- /dev/null +++ b/src/gui/Widget.cpp @@ -0,0 +1,7 @@ +#include "../../include/gui/Widget.hpp" + +namespace gsr { + void Widget::set_position(mgl::vec2f position) { + this->position = position; + } +}
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..537011c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,276 @@ +#include "../include/gui/Button.hpp" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <X11/Xlib.h> +#include <X11/cursorfont.h> +#include <X11/extensions/shape.h> + +#include <mglpp/mglpp.hpp> +#include <mglpp/graphics/Font.hpp> +#include <mglpp/graphics/Text.hpp> +#include <mglpp/graphics/Texture.hpp> +#include <mglpp/graphics/Sprite.hpp> +#include <mglpp/window/Window.hpp> +#include <mglpp/window/Event.hpp> +#include <mglpp/system/MemoryMappedFile.hpp> + +#include <vector> + +extern "C" { +#include <mgl/mgl.h> +} + +static void usage() { + fprintf(stderr, "usage: window-overlay <window>\n"); + exit(1); +} + +static void startup_error(const char *msg) { + fprintf(stderr, "Error: %s\n", msg); + exit(1); +} + +static bool string_to_i64(const char *str, int64_t *result) { + errno = 0; + char *endptr = nullptr; + int64_t res = strtoll(str, &endptr, 0); + if(endptr == str || errno != 0) + return false; + + *result = res; + return true; +} + +int main(int argc, char **argv) { + if(argc != 2) + usage(); + + int64_t target_window; + if(!string_to_i64(argv[1], &target_window)) { + fprintf(stderr, "Error: invalid number '%s' was specific for window argument\n", argv[1]); + return 1; + } + + mgl::Init init; + Display *display = (Display*)mgl_get_context()->connection; + + XWindowAttributes target_win_attr; + memset(&target_win_attr, 0, sizeof(target_win_attr)); + if(!XGetWindowAttributes(display, target_window, &target_win_attr)) { + fprintf(stderr, "Error: window argument %s is not a valid window\n", argv[1]); + return 1; + } + + XSelectInput(display, target_window, StructureNotifyMask); + + mgl::vec2i target_window_size = { target_win_attr.width, target_win_attr.height }; + + mgl::Window::CreateParams window_create_params; + window_create_params.size = target_window_size; + window_create_params.parent_window = target_window; + window_create_params.hidden = true; + + mgl::Window window; + if(!window.create("mglpp", window_create_params)) + startup_error("failed to create window"); + + mgl::MemoryMappedFile title_font_file; + if(!title_font_file.load("fonts/Orbitron-Bold.ttf", mgl::MemoryMappedFile::LoadOptions{true, false})) + startup_error("failed to load file: fonts/Orbitron-Bold.ttf"); + + mgl::MemoryMappedFile font_file; + if(!font_file.load("fonts/Orbitron-Regular.ttf", mgl::MemoryMappedFile::LoadOptions{true, false})) + startup_error("failed to load file: fonts/Orbitron-Regular.ttf"); + + mgl::Font title_font; + if(!title_font.load_from_file(title_font_file, 32)) + startup_error("failed to load font: fonts/Orbitron-Bold.ttf"); + + mgl::Font font; + if(!font.load_from_file(font_file, 20)) + startup_error("failed to load font: fonts/Orbitron-Regular.ttf"); + + mgl::Texture replay_button_texture; + if(!replay_button_texture.load_from_file("images/replay.png")) + startup_error("failed to load texture: images/replay.png"); + + mgl::Texture record_button_texture; + if(!record_button_texture.load_from_file("images/record.png")) + startup_error("failed to load texture: images/record.png"); + + mgl::Texture stream_button_texture; + if(!stream_button_texture.load_from_file("images/stream.png")) + startup_error("failed to load texture: images/stream.png"); + + struct MainButton { + mgl::Text title; + mgl::Text description; + mgl::Sprite icon; + gsr::Button button; + }; + + const char *titles[] = { + "Instant Replay", + "Record", + "Livestream" + }; + + const char *descriptions[] = { + "Off", + "Not recording", + "Not streaming" + }; + + mgl::Texture *textures[] = { + &replay_button_texture, + &record_button_texture, + &stream_button_texture + }; + + std::vector<MainButton> main_buttons; + std::vector<XRectangle> shapes; + + for(int i = 0; i < 3; ++i) { + mgl::Text title(titles[i], {0.0f, 0.0f}, title_font); + title.set_color(mgl::Color(255, 255, 255)); + + mgl::Text description(descriptions[i], {0.0f, 0.0f}, font); + description.set_color(mgl::Color(255, 255, 255, 150)); + + mgl::Sprite sprite(textures[i]); + gsr::Button button({425, 300}); + + MainButton main_button = { + std::move(title), + std::move(description), + std::move(sprite), + std::move(button) + }; + + main_buttons.push_back(std::move(main_button)); + shapes.push_back({}); + } + + // Settings button + shapes.push_back({}); + + const int per_button_width = 425; + const mgl::vec2i overlay_desired_size = { per_button_width * (int)main_buttons.size(), 300 }; + + XGCValues xgcv; + xgcv.foreground = WhitePixel(display, DefaultScreen(display)); + xgcv.line_width = 1; + xgcv.line_style = LineSolid; + + Pixmap shape_pixmap = None; + GC shape_gc = None; + + auto update_overlay_shape = [&]() { + const int main_button_margin = 20; + const mgl::vec2i main_buttons_start_pos = target_window_size/2 - overlay_desired_size/2; + mgl::vec2i main_button_pos = main_buttons_start_pos; + + for(size_t i = 0; i < main_buttons.size(); ++i) { + main_buttons[i].title.set_position( + mgl::vec2f( + main_button_pos.x + per_button_width * 0.5f - main_buttons[i].title.get_bounds().size.x * 0.5f, + main_button_pos.y + main_button_margin).floor()); + + main_buttons[i].description.set_position( + mgl::vec2f( + main_button_pos.x + per_button_width * 0.5f - main_buttons[i].description.get_bounds().size.x * 0.5f, + main_button_pos.y + overlay_desired_size.y - main_buttons[i].description.get_bounds().size.y - main_button_margin).floor()); + + main_buttons[i].icon.set_position( + mgl::vec2f( + main_button_pos.x + per_button_width * 0.5f - main_buttons[i].icon.get_texture()->get_size().x * 0.5f, + main_button_pos.y + overlay_desired_size.y * 0.5f - main_buttons[i].icon.get_texture()->get_size().y * 0.5f).floor()); + + main_buttons[i].button.set_position(main_button_pos.to_vec2f()); + shapes[i] = { + (short)main_button_pos.x, (short)main_button_pos.y, (unsigned short)per_button_width, (unsigned short)overlay_desired_size.y + }; + main_button_pos.x += per_button_width; + } + + const mgl::vec2i settings_button_size(128, 128); + shapes[main_buttons.size()] = { + (short)(main_buttons_start_pos.x + overlay_desired_size.x), (short)(main_buttons_start_pos.y - settings_button_size.y), (unsigned short)settings_button_size.x, (unsigned short)settings_button_size.y + }; + + if(shape_pixmap) { + XFreePixmap(display, shape_pixmap); + shape_pixmap = None; + } + + if(shape_gc) { + XFreeGC(display, shape_gc); + shape_gc = None; + } + + shape_pixmap = XCreatePixmap(display, window.get_system_handle(), target_window_size.x, target_window_size.y, 1); + if(!shape_pixmap) + fprintf(stderr, "Error: failed to create shape pixmap\n"); + + shape_gc = XCreateGC(display, shape_pixmap, 0, &xgcv); + + XSetForeground(display, shape_gc, 0); + XFillRectangle(display, shape_pixmap, shape_gc, 0, 0, target_window_size.x, target_window_size.y); + + XSetForeground(display, shape_gc, 1); + XDrawRectangles(display, shape_pixmap, shape_gc, shapes.data(), shapes.size()); + XFillRectangles(display, shape_pixmap, shape_gc, shapes.data(), shapes.size()); + + XShapeCombineMask(display, window.get_system_handle(), ShapeBounding, 0, 0, shape_pixmap, ShapeSet); + }; + + update_overlay_shape(); + window.set_visible(true); + + Cursor default_cursor = XCreateFontCursor(display, XC_arrow); + + // TODO: Retry if these fail + XGrabPointer(display, window.get_system_handle(), True, ButtonPressMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime); + XGrabKeyboard(display, window.get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime); + + XEvent xev; + mgl::Event event; + + while(window.is_open()) { + if(XCheckTypedWindowEvent(display, target_window, ConfigureNotify, &xev) && (xev.xconfigure.width != target_window_size.x || xev.xconfigure.height != target_window_size.y)) { + while(XCheckTypedWindowEvent(display, target_window, ConfigureNotify, &xev)) {} + target_window_size.x = xev.xconfigure.width; + target_window_size.y = xev.xconfigure.height; + window.set_size(target_window_size); + update_overlay_shape(); + } + + if(window.poll_event(event)) { + for(auto &main_button : main_buttons) { + main_button.button.on_event(event, window); + } + + if(event.type == mgl::Event::KeyPressed) { + if(event.key.code == mgl::Keyboard::Escape) { + window.close(); + break; + } + } + } + + window.clear(mgl::Color(37, 43, 47, 255)); + for(auto &main_button : main_buttons) { + main_button.button.draw(window); + window.draw(main_button.icon); + window.draw(main_button.title); + window.draw(main_button.description); + } + window.display(); + } + + return 0; +} |