diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.cpp | 448 |
1 files changed, 418 insertions, 30 deletions
diff --git a/src/main.cpp b/src/main.cpp index 565eabd..a6f7dae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,9 +16,10 @@ #include <assert.h> #include <unistd.h> #include <limits.h> +#include <poll.h> #include <X11/Xlib.h> -#include <X11/extensions/Xfixes.h> +#include <X11/Xatom.h> #include <X11/extensions/shape.h> extern "C" { @@ -91,19 +92,32 @@ static Bool make_window_sticky(Display* display, Window window) { static void make_window_click_through(Display *display, Window window) { XRectangle rect; memset(&rect, 0, sizeof(rect)); - XserverRegion region = XFixesCreateRegion(display, &rect, 1); - XFixesSetWindowShapeRegion(display, window, ShapeInput, 0, 0, region); - XFixesDestroyRegion(display, region); + XShapeCombineRectangles(display, window, ShapeInput, 0, 0, &rect, 1, ShapeSet, YXBanded); +} + +static void set_window_clip_region(Display *display, Window window, mgl::vec2i pos, mgl::vec2i size) { + if(size.x < 0) + size.x = 0; + if(size.y < 0) + size.y = 0; + XRectangle rectangle = {(short)pos.x, (short)pos.y, (unsigned short)size.x, (unsigned short)size.y}; + XShapeCombineRectangles(display, window, ShapeBounding, 0, 0, &rectangle, 1, ShapeSet, YXBanded); +} + +static bool is_xwayland(Display *display) { + int opcode, event, error; + return XQueryExtension(display, "XWAYLAND", &opcode, &event, &error); } static void usage() { - fprintf(stderr, "usage: gsr-notify <--text text> <--timeout timeout> [--icon filepath] [--icon-color color] [--bg-color color]\n"); + fprintf(stderr, "usage: gsr-notify --text text --timeout timeout [--icon filepath] [--icon-color color] [--bg-color color] [--monitor monitor]\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " --text The text to display in the notification. Required.\n"); fprintf(stderr, " --timeout The time to display the notification in seconds (excluding animation time), expected to be a floating point number. Required.\n"); - fprintf(stderr, " --icon A path to an image file to display on the left side of the text. This can also be either \"record\", \"replay\" or \"stream\" to use built-in images. Optional.\n"); + fprintf(stderr, " --icon A path to an image file to display on the left side of the text. This can also be either \"record\", \"replay\", \"stream\" or \"screenshot\" to use built-in images. Optional.\n"); fprintf(stderr, " --icon-color The color to display the icon as in hex format, for example FF0000. Optional, set to FFFFFF by default.\n"); fprintf(stderr, " --bg-color The notification background (and side bar) color in hex format, for example FF0000. Optional, set to 76b900 by default.\n"); + fprintf(stderr, " --monitor The monitor to display the notification on. This is the name of the monitor as displayed by xrandr. This can be \"auto\", in which case the focused monitor is used. Optional, set to \"auto\" by default.\n"); fprintf(stderr, "examples:\n"); fprintf(stderr, " gsr-notify --text 'Recording has started' --timeout 3.0\n"); fprintf(stderr, " gsr-notify --text 'Recording has started' --timeout 3.0 --icon record\n"); @@ -145,22 +159,339 @@ static mgl::Color parse_hex_color(const char *str) { } // Returns the first monitor if not found. Assumes there is at least one monitor connected. -static const mgl_monitor* find_monitor_by_cursor_position(mgl::Window &window) { +static const mgl_monitor* find_monitor_at_position(mgl::Window &window, mgl::vec2i pos) { const mgl_window *win = window.internal_window(); assert(win->num_monitors > 0); for(int i = 0; i < win->num_monitors; ++i) { const mgl_monitor *mon = &win->monitors[i]; - if(mgl::IntRect({ mon->pos.x, mon->pos.y }, { mon->size.x, mon->size.y }).contains({ win->cursor_position.x, win->cursor_position.y })) + if(mgl::IntRect({ mon->pos.x, mon->pos.y }, { mon->size.x, mon->size.y }).contains(pos)) return mon; } return &win->monitors[0]; } +static const mgl_monitor* get_monitor_by_name(mgl::Window &window, const char *name) { + const mgl_window *win = window.internal_window(); + for(int i = 0; i < win->num_monitors; ++i) { + const mgl_monitor *mon = &win->monitors[i]; + if(strcmp(mon->name, name) == 0) + return mon; + } + return nullptr; +} + +static void print_monitor_names(mgl::Window &window) { + const mgl_window *win = window.internal_window(); + for(int i = 0; i < win->num_monitors; ++i) { + const mgl_monitor *mon = &win->monitors[i]; + fprintf(stderr, " %s\n", mon->name); + } +} + +static bool window_has_atom(Display *dpy, Window window, Atom atom) { + Atom type; + unsigned long len, bytes_left; + int format; + unsigned char *properties = NULL; + if(XGetWindowProperty(dpy, window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success) + return false; + + if(properties) + XFree(properties); + + return type != None; +} + +static bool window_is_user_program(Display *dpy, Window window) { + const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False); + const Atom wm_state_atom = XInternAtom(dpy, "WM_STATE", False); + return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom); +} + +static Window window_get_target_window_child(Display *display, Window window) { + if(window == None) + return None; + + if(window_is_user_program(display, window)) + return window; + + Window root; + Window parent; + Window *children = nullptr; + unsigned int num_children = 0; + if(!XQueryTree(display, window, &root, &parent, &children, &num_children) || !children) + return None; + + Window found_window = None; + for(int i = num_children - 1; i >= 0; --i) { + if(children[i] && window_is_user_program(display, children[i])) { + found_window = children[i]; + goto finished; + } + } + + for(int i = num_children - 1; i >= 0; --i) { + if(children[i]) { + Window win = window_get_target_window_child(display, children[i]); + if(win) { + found_window = win; + goto finished; + } + } + } + + finished: + XFree(children); + return found_window; +} + +static bool window_has_title(Display *dpy, Window window) { + bool result = false; + const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); + const Atom wm_name_atom = XInternAtom(dpy, "WM_NAME", False); + const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False); + + Atom type = None; + int format = 0; + unsigned long num_items = 0; + unsigned long bytes_left = 0; + unsigned char *data = NULL; + XGetWindowProperty(dpy, window, net_wm_name_atom, 0, 1024, False, utf8_string_atom, &type, &format, &num_items, &bytes_left, &data); + + if(type == utf8_string_atom && format == 8 && data) { + result = true; + goto done; + } + + if(data) + XFree(data); + + type = None; + format = 0; + num_items = 0; + bytes_left = 0; + data = NULL; + XGetWindowProperty(dpy, window, wm_name_atom, 0, 1024, False, 0, &type, &format, &num_items, &bytes_left, &data); + + if((type == XA_STRING || type == utf8_string_atom) && data) { + result = true; + goto done; + } + + done: + if(data) + XFree(data); + return result; +} + +static mgl::vec2i get_cursor_position(Display *dpy, Window *window) { + Window root_window = None; + *window = None; + int dummy_i; + unsigned int dummy_u; + mgl::vec2i root_pos; + XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u); + + const Window direct_window = *window; + *window = window_get_target_window_child(dpy, *window); + // HACK: Count some other x11 windows as having an x11 window focused. Some games seem to create an Input window and that gets focused. + if(!*window) { + XWindowAttributes attr; + memset(&attr, 0, sizeof(attr)); + XGetWindowAttributes(dpy, direct_window, &attr); + if(attr.c_class == InputOnly && !window_has_title(dpy, direct_window)) + *window = direct_window; + } + return root_pos; +} + +typedef struct { + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; +} MotifHints; + +#define MWM_HINTS_DECORATIONS 2 + +#define MWM_DECOR_NONE 0 +#define MWM_DECOR_ALL 1 + +static void window_set_decorations_visible(Display *display, Window window, bool visible) { + const Atom motif_wm_hints_atom = XInternAtom(display, "_MOTIF_WM_HINTS", False); + MotifHints motif_hints; + memset(&motif_hints, 0, sizeof(motif_hints)); + motif_hints.flags = MWM_HINTS_DECORATIONS; + motif_hints.decorations = visible ? MWM_DECOR_ALL : MWM_DECOR_NONE; + XChangeProperty(display, window, motif_wm_hints_atom, motif_wm_hints_atom, 32, PropModeReplace, (unsigned char*)&motif_hints, sizeof(motif_hints) / sizeof(long)); +} + +static bool create_window_get_center_position_kde(Display *display, mgl::vec2i &position) { + const int size = 1; + XSetWindowAttributes window_attr; + window_attr.event_mask = StructureNotifyMask; + window_attr.background_pixel = 0; + const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr); + if(!window) + return false; + + const Atom net_wm_window_type_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); + const Atom net_wm_window_type_notification_atom = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False); + const Atom net_wm_window_type_utility = XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + const Atom net_wm_window_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False); + + const Atom window_type_atoms[2] = { + net_wm_window_type_notification_atom, + net_wm_window_type_utility + }; + XChangeProperty(display, window, net_wm_window_type_atom, XA_ATOM, 32, PropModeReplace, (const unsigned char*)window_type_atoms, 2L); + + const double alpha = 0.0; + const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha); + XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); + + window_set_decorations_visible(display, window, false); + + XSizeHints *size_hints = XAllocSizeHints(); + size_hints->width = size; + size_hints->height = size; + size_hints->min_width = size; + size_hints->min_height = size; + size_hints->max_width = size; + size_hints->max_height = size; + size_hints->flags = PSize | PMinSize | PMaxSize; + XSetWMNormalHints(display, window, size_hints); + XFree(size_hints); + + XMapWindow(display, window); + XFlush(display); + + bool got_data = false; + const int x_fd = XConnectionNumber(display); + XEvent xev; + while(true) { + struct pollfd poll_fd; + poll_fd.fd = x_fd; + poll_fd.events = POLLIN; + poll_fd.revents = 0; + const int fds_ready = poll(&poll_fd, 1, 200); + if(fds_ready == 0) { + fprintf(stderr, "Error: timed out waiting for ConfigureNotify after XCreateWindow\n"); + break; + } else if(fds_ready == -1 || !(poll_fd.revents & POLLIN)) { + continue; + } + + while(XPending(display)) { + XNextEvent(display, &xev); + if(xev.type == ConfigureNotify && xev.xconfigure.window == window) { + got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0; + position.x = xev.xconfigure.x + xev.xconfigure.width / 2; + position.y = xev.xconfigure.y + xev.xconfigure.height / 2; + goto done; + } + } + } + + done: + XDestroyWindow(display, window); + XFlush(display); + + return got_data; +} + +static bool create_window_get_center_position_gnome(Display *display, mgl::vec2i &position) { + const int size = 32; + XSetWindowAttributes window_attr; + window_attr.event_mask = StructureNotifyMask | ExposureMask; + window_attr.background_pixel = 0; + const Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0, size, size, 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixel | CWEventMask, &window_attr); + if(!window) + return false; + + const Atom net_wm_window_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False); + const double alpha = 0.0; + const unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha); + XChangeProperty(display, window, net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1L); + + window_set_decorations_visible(display, window, false); + + XSizeHints *size_hints = XAllocSizeHints(); + size_hints->width = size; + size_hints->height = size; + size_hints->min_width = size; + size_hints->min_height = size; + size_hints->max_width = size; + size_hints->max_height = size; + size_hints->flags = PSize | PMinSize | PMaxSize; + XSetWMNormalHints(display, window, size_hints); + XFree(size_hints); + + XMapWindow(display, window); + XFlush(display); + + bool got_data = false; + const int x_fd = XConnectionNumber(display); + XEvent xev; + while(true) { + struct pollfd poll_fd; + poll_fd.fd = x_fd; + poll_fd.events = POLLIN; + poll_fd.revents = 0; + const int fds_ready = poll(&poll_fd, 1, 200); + if(fds_ready == 0) { + fprintf(stderr, "Error: timed out waiting for MapNotify/ConfigureNotify after XCreateWindow\n"); + break; + } else if(fds_ready == -1 || !(poll_fd.revents & POLLIN)) { + continue; + } + + while(XPending(display)) { + XNextEvent(display, &xev); + if(xev.type == MapNotify && xev.xmap.window == window) { + int x = 0; + int y = 0; + Window w = None; + XTranslateCoordinates(display, window, DefaultRootWindow(display), 0, 0, &x, &y, &w); + + got_data = x > 0 && y > 0; + position.x = x + size / 2; + position.y = y + size / 2; + if(got_data) + goto done; + } else if(xev.type == ConfigureNotify && xev.xconfigure.window == window) { + got_data = xev.xconfigure.x > 0 && xev.xconfigure.y > 0; + position.x = xev.xconfigure.x + xev.xconfigure.width / 2; + position.y = xev.xconfigure.y + xev.xconfigure.height / 2; + if(got_data) + goto done; + } + } + } + + done: + XDestroyWindow(display, window); + XFlush(display); + + return got_data; +} + +static mgl::vec2i create_window_get_center_position(Display *display) { + mgl::vec2i pos; + if(!create_window_get_center_position_kde(display, pos)) { + pos.x = 0; + pos.y = 0; + create_window_get_center_position_gnome(display, pos); + } + return pos; +} + int main(int argc, char **argv) { setlocale(LC_ALL, "C"); // Sigh... stupid C std::string resources_path; - if(access("sibs-build", F_OK) == 0) { + if(access("sibs-build/linux_x86_64/debug/gsr-notify", F_OK) == 0) { resources_path = "./"; } else { #ifdef GSR_NOTIFY_RESOURCES_PATH @@ -182,6 +513,7 @@ int main(int argc, char **argv) { {"--icon", nullptr}, {"--icon-color", nullptr}, {"--bg-color", nullptr}, + {"--monitor", nullptr}, }; for(int i = 1; i < argc; i += 2) { @@ -204,6 +536,7 @@ int main(int argc, char **argv) { const char *icon_filepath = args["--icon"]; const char *icon_color_str = args["--icon-color"]; const char *bg_color_str = args["--bg-color"]; + const char *monitor_str = args["--monitor"]; if(!notification_text) { fprintf(stderr, "error: missing required option '--text'\n"); @@ -221,32 +554,54 @@ int main(int argc, char **argv) { usage(); } + if(!monitor_str) + monitor_str = "auto"; + const mgl::Color icon_color = parse_hex_color(icon_color_str ? icon_color_str : "FFFFFF"); const mgl::Color bg_color = parse_hex_color(bg_color_str ? bg_color_str : "76b900"); mgl::Init init; + Display *display = (Display*)mgl_get_context()->connection; + const bool wayland = is_xwayland(display); + const bool use_transparency = wayland; mgl::Window::CreateParams window_create_params; - window_create_params.size = { 1280, 720 }; + window_create_params.size = { 32, 32 }; window_create_params.min_size = window_create_params.size; window_create_params.max_size = window_create_params.size; window_create_params.hidden = true; window_create_params.override_redirect = true; - window_create_params.background_color = bg_color; + window_create_params.support_alpha = true; + window_create_params.background_color = use_transparency ? mgl::Color(0, 0, 0, 0) : bg_color; window_create_params.hide_decorations = true; window_create_params.window_type = MGL_WINDOW_TYPE_NOTIFICATION; + window_create_params.graphics_api = MGL_GRAPHICS_API_GLX; mgl::Window window; - if(!window.create("GPU Screen Recorder Notification", window_create_params)) + if(!window.create("gsr notify", window_create_params)) return 1; - mgl_window *win = window.internal_window(); + const mgl_window *win = window.internal_window(); if(win->num_monitors == 0) { fprintf(stderr, "Error: no monitors found\n"); exit(1); } - const mgl_monitor *focused_monitor = find_monitor_by_cursor_position(window); + const mgl_monitor *focused_monitor = nullptr; + if(strcmp(monitor_str, "auto") == 0) { + // The cursor position is wrong on wayland if an x11 window is not focused. On wayland we instead create a window and get the position where the wayland compositor puts it + Window x11_cursor_window = None; + const mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window); + const mgl::vec2i monitor_position_query_value = (x11_cursor_window || !wayland) ? cursor_position : create_window_get_center_position(display); + focused_monitor = find_monitor_at_position(window, monitor_position_query_value); + } else { + focused_monitor = get_monitor_by_name(window, monitor_str); + if(!focused_monitor) { + fprintf(stderr, "Error: no monitor with the name \"%s\" was found. Expected one of:\n", monitor_str); + print_monitor_names(window); + exit(1); + } + } const std::string noto_sans_bold_filepath = resources_path + "fonts/NotoSans-Bold.ttf"; mgl::MemoryMappedFile font_file; @@ -266,8 +621,10 @@ int main(int argc, char **argv) { icon_filepath_str = resources_path + "images/replay.png"; else if(icon_filepath_str == "stream") icon_filepath_str = resources_path + "images/stream.png"; + else if(icon_filepath_str == "screenshot") + icon_filepath_str = resources_path + "images/screenshot.png"; - if(!logo_texture.load_from_file(icon_filepath_str.c_str(), {false, false, true})) { + if(!logo_texture.load_from_file(icon_filepath_str.c_str(), {false, false, MGL_TEXTURE_SCALE_LINEAR_MIPMAP})) { fprintf(stderr, "Warning: failed to load icon\n"); } } @@ -281,8 +638,9 @@ int main(int argc, char **argv) { text.set_color(mgl::Color(255, 255, 255, 0)); mgl::Sprite logo_sprite; - float logo_sprite_padding_x = 20.0f; + float logo_sprite_padding_x = (int)(window_height * 0.4f); float padding_between_icon_and_text_x = 0.0f; + float padding_right_side = (int)(window_height * 0.4f); if(logo_texture.is_valid()) { logo_sprite.set_texture(&logo_texture); logo_sprite.set_color(mgl::Color(icon_color.r, icon_color.g, icon_color.b, 0)); @@ -291,18 +649,24 @@ int main(int argc, char **argv) { padding_between_icon_and_text_x = (int)(logo_sprite_padding_x * 0.7f); } - Display *display = (Display*)mgl_get_context()->connection; + unsigned char data = 1; // Prefer not being composed to not reduce display fps on AMD when an application is using 100% of GPU + XChangeProperty(display, (Window)window.get_system_handle(), XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); + + data = 1; + XChangeProperty(display, (Window)window.get_system_handle(), XInternAtom(display, "GAMESCOPE_EXTERNAL_OVERLAY", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); // TODO: Make sure the notification always stays on top. Test with starting the notification and then opening youtube in fullscreen. - const int window_width = content_padding_left + logo_sprite_padding_x + logo_sprite.get_size().x + padding_between_icon_and_text_x + text.get_bounds().size.x + logo_sprite_padding_x + padding_between_icon_and_text_x; + const int window_width = content_padding_left + logo_sprite_padding_x + logo_sprite.get_size().x + padding_between_icon_and_text_x + text.get_bounds().size.x + padding_right_side; const mgl::vec2i window_size{window_width, window_height}; - const mgl::vec2i window_start_position{focused_monitor->pos.x + focused_monitor->size.x, focused_monitor->pos.y + window_size.y}; + const mgl::vec2i window_start_position{focused_monitor->pos.x + focused_monitor->size.x - window_size.x, focused_monitor->pos.y + window_size.y}; window.set_size_limits(window_size, window_size); window.set_size(window_size); + if(!use_transparency) + set_window_clip_region(display, (Window)window.get_system_handle(), {0, 0}, {0, 0}); window.set_position(window_start_position); - make_window_click_through(display, window.get_system_handle()); + make_window_click_through(display, (Window)window.get_system_handle()); window.set_visible(true); - make_window_sticky(display, window.get_system_handle()); + make_window_sticky(display, (Window)window.get_system_handle()); const int slide_window_start_x = focused_monitor->pos.x + focused_monitor->size.x; const int slide_window_end_x = focused_monitor->pos.x + focused_monitor->size.x - window_size.x; @@ -322,6 +686,10 @@ int main(int argc, char **argv) { mgl::Clock state_timer; + mgl::Rectangle transparency_bg(window_size.to_vec2f()); + transparency_bg.set_color(bg_color); + transparency_bg.set_position({(float)window_size.x, 0.0f}); + mgl::Rectangle content_bg(window_size.to_vec2f() - mgl::vec2f(content_padding_left, 0.0f)); content_bg.set_color(mgl::Color(0, 0, 0)); @@ -337,7 +705,7 @@ int main(int argc, char **argv) { const auto slide_content_handler = [&](double interpolate_start, double interpolate_end, double interpolation) { double new_slide_x = interpolate(interpolate_start, interpolate_end, interpolation); content_bg.set_position(mgl::vec2f(new_slide_x, content_bg_start_position.y).floor()); - logo_sprite.set_position((content_bg.get_position() + mgl::vec2f(logo_sprite_padding_x, content_bg.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f)).floor()); + logo_sprite.set_position((content_bg.get_position() + mgl::vec2f(content_padding_left + logo_sprite_padding_x, content_bg.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f)).floor()); const float content_space_left_pos_x = logo_sprite.get_position().x + logo_sprite.get_size().x + padding_between_icon_and_text_x; //const float content_space_left_x = content_bg.get_size().x - content_space_left_pos_x; text.set_position((mgl::vec2f(content_space_left_pos_x, content_bg.get_position().y) + mgl::vec2f(0.0f, content_bg.get_size().y) * 0.5f - mgl::vec2f(0.0f, text.get_bounds().size.y) * 0.5f).floor()); @@ -353,8 +721,14 @@ int main(int argc, char **argv) { switch(current_state.state) { case State::SLIDE_IN_WINDOW: { - double new_slide_x = interpolate(slide_window_start_x, slide_window_end_x, state_interpolation); - window.set_position(mgl::vec2i(new_slide_x, window_start_position.y)); + const double new_slide_x = interpolate(slide_window_start_x, slide_window_end_x, state_interpolation); + const mgl::vec2i window_clip(std::max(0.0, slide_window_start_x - new_slide_x), window_size.y); + if(use_transparency) { + transparency_bg.set_size(window_clip.floor().to_vec2f()); + } else { + set_window_clip_region(display, (Window)window.get_system_handle(), {window_size.x - window_clip.x, 0}, window_clip); + XFlush(display); + } break; } case State::SLIDE_IN_CONTENT: { @@ -362,13 +736,13 @@ int main(int argc, char **argv) { break; } case State::FADE_IN_CONTENT: { - double new_alpha = interpolate(content_start_alpha, content_end_alpha, state_interpolation); + const double new_alpha = interpolate(content_start_alpha, content_end_alpha, state_interpolation); text.set_color(mgl::Color(255, 255, 255, new_alpha * 255.0f)); logo_sprite.set_color(mgl::Color(icon_color.r, icon_color.g, icon_color.b, new_alpha * 255.0f)); break; } case State::FADE_OUT_CONTENT: { - double new_alpha = interpolate(content_end_alpha, content_start_alpha, state_interpolation); + const double new_alpha = interpolate(content_end_alpha, content_start_alpha, state_interpolation); text.set_color(mgl::Color(255, 255, 255, new_alpha * 255.0f)); logo_sprite.set_color(mgl::Color(icon_color.r, icon_color.g, icon_color.b, new_alpha * 255.0f)); break; @@ -378,8 +752,14 @@ int main(int argc, char **argv) { break; } case State::SLIDE_OUT_WINDOW: { - double new_slide_x = interpolate(slide_window_end_x, slide_window_start_x, state_interpolation); - window.set_position(mgl::vec2i(new_slide_x, window_start_position.y)); + const double new_slide_x = interpolate(slide_window_end_x, slide_window_start_x, state_interpolation); + const mgl::vec2i window_clip(std::max(0.0, slide_window_start_x - new_slide_x), window_size.y); + if(use_transparency) { + transparency_bg.set_size(window_clip.floor().to_vec2f()); + } else { + set_window_clip_region(display, (Window)window.get_system_handle(), {window_size.x - window_clip.x, 0}, window_clip); + XFlush(display); + } break; } case State::PAUSE: { @@ -387,7 +767,13 @@ int main(int argc, char **argv) { } } - window.clear(bg_color); + if(use_transparency) { + window.clear(mgl::Color(0, 0, 0, 0)); + transparency_bg.set_position(mgl::vec2f(window_size.x - transparency_bg.get_size().x, 0.0f)); + window.draw(transparency_bg); + } else { + window.clear(bg_color); + } window.draw(content_bg); window.draw(logo_sprite); window.draw(text); @@ -396,8 +782,10 @@ int main(int argc, char **argv) { if(state_interpolation >= 1.0) { state_timer.restart(); ++current_state_index; - if(current_state_index >= (int)states_to_execute_in_order.size()) + if(current_state_index >= (int)states_to_execute_in_order.size()) { window.close(); + break; + } } } } |