diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f20cb2..cdfa8f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: if_true: >- git make + unzip mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja @@ -41,6 +42,7 @@ jobs: if_false: >- git make + unzip mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja @@ -95,6 +97,11 @@ jobs: mkdir -p 16x16/devices 24x24/devices 32x32/devices 48x48/devices 64x64/devices 96x96/devices scalable/devices mkdir -p 16x16/status 24x24/status 32x32/status 48x48/status 64x64/status 96x96/status scalable/status cd ${GITHUB_WORKSPACE} + wget https://github.com/rtlewis1/GTK/archive/refs/heads/Material-Black-Colors-Desktop.zip + unzip Material-Black-Colors-Desktop.zip 'GTK-Material-Black-Colors-Desktop/Material-Black-Cherry/**/*' + mkdir -p ${artifact_dir}/share/themes + mv ./GTK-Material-Black-Colors-Desktop/Material-Black-Cherry ${artifact_dir}/share/themes/Material-Black-Cherry + cp -r ci/tree/. ${artifact_dir} cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/index.theme ${artifact_dir}/share/icons/Adwaita/index.theme cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/16x16/%.symbolic.png || : cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/24x24/%.symbolic.png || : diff --git a/.readme/s5.png b/.readme/s5.png new file mode 100644 index 0000000..b49c0c5 Binary files /dev/null and b/.readme/s5.png differ diff --git a/.readme/s6.png b/.readme/s6.png new file mode 100644 index 0000000..3152293 Binary files /dev/null and b/.readme/s6.png differ diff --git a/.readme/s7.png b/.readme/s7.png new file mode 100644 index 0000000..e605c55 Binary files /dev/null and b/.readme/s7.png differ diff --git a/.readme/s8.png b/.readme/s8.png new file mode 100644 index 0000000..c9d3157 Binary files /dev/null and b/.readme/s8.png differ diff --git a/README.md b/README.md index 89e7d3d..b47e895 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,16 @@ --- Alternative Discord client made in C++ with GTK - + + + + + + + + + +
@@ -11,7 +20,7 @@ Current features: * Not Electron * Voice support * Handles most types of chat messages including embeds, images, and replies -* Completely styleable/customizable with CSS (if you have a system GTK theme it won't really use it though) +* Completely styleable/customizable * Identifies to Discord as the web client unlike other clients so less likely to be falsely flagged as spam1 * Set status * Unread and mention indicators @@ -301,10 +310,8 @@ For example, memory_db would be set by adding `memory_db = true` under the line | Setting | Type | Description | |-------------------------|--------|-----------------------------------------------------| -| `linkcolor` | string | color to use for links in messages | | `expandercolor` | string | color to use for the expander in the channel list | | `nsfwchannelcolor` | string | color to use for NSFW channels in the channel list | -| `channelcolor` | string | color to use for SFW channels in the channel list | | `mentionbadgecolor` | string | background color for mention badges | | `mentionbadgetextcolor` | string | color to use for number displayed on mention badges | | `unreadcolor` | string | color to use for the unread indicator | diff --git a/ci/msys-deps.txt b/ci/msys-deps.txt index 39172f0..d37418b 100644 --- a/ci/msys-deps.txt +++ b/ci/msys-deps.txt @@ -38,6 +38,7 @@ /bin/libidn2-0.dll /bin/libintl-8.dll /bin/libjpeg-8.dll +/bin/liblzma-5.dll /bin/libnghttp2-14.dll /bin/libopus-0.dll /bin/libpango-1.0-0.dll @@ -49,6 +50,7 @@ /bin/libpixman-1-0.dll /bin/libpng16-16.dll /bin/libpsl-5.dll +/bin/librsvg-2-2.dll /bin/libsigc-2.0-0.dll /bin/libsodium-26.dll /bin/libspdlog.dll @@ -59,6 +61,7 @@ /bin/libthai-0.dll /bin/libunistring-5.dll /bin/libwinpthread-1.dll +/bin/libxml2-2.dll /bin/libzstd.dll /bin/zlib1.dll /../usr/bin/msys-2.0.dll diff --git a/ci/tree/etc/gtk-3.0/settings.ini b/ci/tree/etc/gtk-3.0/settings.ini new file mode 100644 index 0000000..a148875 --- /dev/null +++ b/ci/tree/etc/gtk-3.0/settings.ini @@ -0,0 +1,2 @@ +[Settings] +gtk-theme-name=Material-Black-Cherry diff --git a/res/css/application-low-priority.css b/res/css/application-low-priority.css deleted file mode 100644 index 130033f..0000000 --- a/res/css/application-low-priority.css +++ /dev/null @@ -1,97 +0,0 @@ -/* -application wide stuff -has to be separate to allow main.css to override certain things -*/ - -.app-window label:not(:disabled) { - color: @text_color; -} - -.app-window entry { - background: @secondary_color; - color: @text_color; - border: 1px solid #1c2e40; -} - -.app-window button { - background: @secondary_color; - color: @text_color; - text-shadow: none; - box-shadow: none; -} - -.app-window button:checked { - border-top: 0px; - border-left: 0px; - border-right: 0px; - border-bottom: 3px solid #39a2ed; - color: #ffffff; -} - -.app-window button:not(:checked) { - border: 3px #0000ff; -} - -.app-window.background { - background: @background_color; -} - -.app-window treeview { - color: @text_color; -} - -.app-window treeview:not(:selected) { - background: @secondary_color; -} - -.app-window list, .app-popup list { - background: @secondary_color; -} - -.app-window paned separator { - background: @background_color; -} - -.app-window scrollbar { - background: @background_color; - border-left: 1px solid transparent; -} - -.app-window menubar, menu { - background: @background_color; - color: #cccccc; -} - -.app-window textview text { - caret-color: #ababab; -} - -.app-window check, -.app-window radio { - background-clip: padding-box; - background: @secondary_color; - border-color: #070707; - box-shadow: 0 1px rgba(0, 0, 0, 0); - color: #dddddd; -} - -.app-window check:checked, -.app-window radio:checked { - background-clip: border-box; - background: #0b4285; - border-color: #092444; - box-shadow: 0 1px rgba(0, 0, 0, 0); - color: #dddddd; -} - -.app-window colorswatch { - box-shadow: 0 1px rgba(0, 0, 0, 0); -} - -.app-window scale { - padding-top: 0px; - padding-bottom: 0px; - margin-top: 0px; - margin-bottom: 0px; - color: @text_color; -} diff --git a/res/css/bare.css b/res/css/bare.css deleted file mode 100644 index 80ef2f8..0000000 --- a/res/css/bare.css +++ /dev/null @@ -1,35 +0,0 @@ -.embed { - border-radius: 5px; - padding: 10px; -} - -.embed-footer { - margin-top: 5px; - font-size: 11px; -} - -.embed-author { - margin-bottom: 10px; - font-size: 12px; -} - -.message-attachment-box { - border: 1px solid #aaaaaa; - padding: 2px 5px 2px 5px; -} - -.status-indicator.dnd { - color: #982929; -} - -.status-indicator.online { - color: #43B581; -} - -.status-indicator.offline { - color: #808080; -} - -.status-indicator.idle { - color: #FAA61A; -} diff --git a/res/css/main.css b/res/css/main.css index 2cc4727..ae7bf60 100644 --- a/res/css/main.css +++ b/res/css/main.css @@ -1,12 +1,7 @@ -@define-color background_color #141414; -@define-color secondary_color #111111; -@define-color text_color #fbfbfb; - .embed { - background-color: rgba(0, 0, 0, 0.2); - color: #cbcbcb; border-radius: 5px; padding: 10px; + background-color: rgba(0.0, 0.0, 0.0, 0.1); } .embed-footer { @@ -19,140 +14,7 @@ font-size: 12px; } -.channel-list { - background-color: @secondary_color; -} - -.channel-row-label { - padding: 5px; -} - -.channel-row-label, .channel-row-label text { - color: @text_color; - background: rgba(0, 0, 0, 0); -} - -.channel-row-label.nsfw text { - color: #ed6666; -} - -.channel-row:focus { - background-color: #34495e; -} - -.channel-row-category { - padding-left: 15px; - color: #ff5370; -} - -.channel-row-channel { - padding-left: 30px; -} - -.messages, .message-container { - background-color: @background_color; -} - -.messages { - padding: 15px; -} - -.message-container-extra { - color: #78909c; -} - -.message-container-timestamp { - color: #78909c; -} - -.message-text { - /* this isnt stricly necessary but it fixes emoji clipping */ - padding-bottom: 5px; -} - -.message-text:not(.failed) text, .message-reply { - color: @text_color; -} - -.message-text.pending text { - color: shade(@text_color, 0.5); -} - -.message-text.failed text { - color: #b72d4f; -} - -.message-reply { - border-left: 2px solid gray; - padding-left: 20px; - padding-top: 6px; - padding-bottom: 6px; - opacity: 0.7; -} - -.message-text + .message-text { - padding-top: 5px; -} - -.message-text text { - background-color: @background_color; -} - -.message-input, .message-input textview, .message-input textview text { - background-color: #242424; - color: #adadad; - border-radius: 3px; - border: 1px solid transparent; -} - -.message-input { - border: 1px solid #444444; - margin-right: 15px; -} - -.message-input.replying { - border: 1px solid #026FB9; -} - -.message-input.editing { - border: 1px solid #b9026f; -} - -.message-input.bad-input { - border: 1px solid #dd3300; -} - -.message-input-browse-icon { - color: #b9bbbe; - margin-left: 5px; - margin-top: 11px; -} - -/* i dont think theres a way to circumvent having to do this to adjust around the browse icon */ -.message-input:not(.with-browser-icon) { - padding: 0px 0px 0px 5px; -} - -.message-input.with-browse-icon { - padding: 0px 0px 0px 30px; -} - -.members { - background-color: @background_color; -} - -.members-row-label { - color: @text_color; - padding: 5px; -} - -.members-row-member { - padding: 0; - padding-left: 15px; -} - .message-attachment-box { - color: #aaaaaa; border: 1px solid #aaaaaa; padding: 2px 5px 2px 5px; } @@ -161,61 +23,6 @@ margin: 5px; } -.message-component { - margin: 5px; -} - -.message-component.primary { - background: #5865F2; -} - -.message-component.secondary, .message-component.link { - background: #4F545C; -} - -.message-component.success { - background: #43B581; -} - -.message-component.danger { - background: #F04747; -} - -.reaction-box { - padding: 2px 5px 2px 5px; - margin: 0px 0px 0px 0px; - background-color: rgba(0.4, 0.4, 0.4, 0.4); - border-radius: 5px; - border: 1px solid transparent; -} - -.reaction-box.reacted { - border: 1px solid white; -} - -.reaction-count { - color: @text_color; -} - -.completer { - background-color: @secondary_color; - padding: 5px; -} - -.completer-entry { - color: @text_color; -} - -.completer-entry-image { - margin-right: 6px; -} - -.typing-indicator { - margin-top: 10px; - margin-bottom: -7px; - color: @text_color; -} - .status-indicator.dnd { color: #982929; } @@ -232,6 +39,26 @@ color: #FAA61A; } +.message-input textview, .message-input textview text { + background-color: inherit; +} + +.message-text, .message-text text { + background: inherit; +} + +.message-input textview { + padding: 10px 5px; +} + +.message-reply { + border-left: 2px solid gray; + padding-left: 10px; + padding-top: 6px; + padding-bottom: 6px; + opacity: 0.8; +} + .profile-main-container { padding: 20px; } @@ -242,31 +69,13 @@ } .profile-username-nondisplay { - margin-left: 10px; -} - -.profile-badge { - margin-right: 10px; + margin-left: 10px; } .profile-switcher { padding-top: 5px; } -.profile-connections { - margin-top: 10px; -} - -.profile-connection { - background: @secondary_color; - border-radius: 15px; - margin-right: 20px; -} - -.profile-connection box { - padding: 5px; -} - .profile-stack { padding-top: 5px; } @@ -276,126 +85,63 @@ padding-bottom: 5px; } -.profile-notes-text, .profile-notes-text text { - background: @secondary_color; -} - -.profile-notes-text text { - border-radius: 5px; - border: 1px solid #36515e; - color: @text_color; - padding-bottom: 5px; -} - .profile-badges { padding-left: 5px; } -.guild-members-pane-info { - padding: 10px; +.profile-badge { + margin-right: 5px; } -.drag-hover-top { - background: linear-gradient(to bottom, rgba(255, 66, 66, 0.65) 0%, rgba(0, 0, 0, 0) 35%); -} - -.drag-hover-bottom { - background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 65%, rgba(255, 66, 66, 0.65) 100%); -} - -.friends-list list { - background: @background_color; - padding-left: 10px; -} - -.friends-list-row-bot { - color: #ff0000; -} - -.channel-tab-switcher .box { - margin: -7px -1px -7px -1px; - background: #2a2a2a; - border: 1px solid black; -} - -.channel-tab-switcher tab:hover { - box-shadow: inset 0 -6px #17633e; -} - -.channel-tab-switcher tab:checked { - box-shadow: inset 0 -6px #2feb90; -} - -.channel-tab-switcher tab { - background: #1A1A1A; - border: 1px solid #808080; - min-height: 35px; -} - -.channel-tab-switcher tab.needs-attention:not(:checked) { - font-weight: bold; - animation: 150ms ease-in; - /* background-image: radial-gradient(ellipse at bottom, #FF5370, #1A1A1A 30%); */ - box-shadow: inset 0 -6px red; -} - -.channel-tab-switcher tab > button { - border: none; - padding: 0; - margin: 5px; - min-width: 16px; - min-height: 16px; - color: #FF5370; - background-color: rgba(0.21, 0.21, 0.21, 0.5); -} - -.channel-tab-switcher tab > button:hover { - background-color: alpha(#ff0000, 0.5); -} - -.message-progress { - border: none; - margin-bottom: -8px; -} - -.message-progress trough { - border: none; - background-color: transparent; -} - -.message-progress progress { - border: none; - background-color: #dd3300; - margin-left: 1px; -} - -.voice-info { - background-color: #0B0B0B; - padding: 5px; - border: 1px solid #202020; -} - -.voice-info-disconnect-image { - color: #DDDDDD; -} - -.voice-info-status { - font-weight: bold; -} - -.voice-info-location { - -} - -.voice-state-server { - color: red; -} - -spinbutton { - color: @text_color; +.profile-connections { margin-top: 10px; } -.emoji-picker, .emoji-picker stack box { - background-color: @background_color; +.profile-connection { + padding: 5px; + border-radius: 10px; + border: 1px solid @theme_fg_color; +} + +.profile-connection-image { + padding-right: 5px; +} + +.reaction-box { + padding: 2px 5px 2px 5px; + border-radius: 5px; + border: 1px solid @theme_fg_color; +} + +.reaction-box.reacted { + border: 1px solid #5865f2; + background-color: alpha(@theme_selected_bg_color, 0.5); +} + +.set-status-dialog .dialog-vbox { + padding: 5px; +} + +.set-status-dialog .dialog-action-area { + margin: 10px 5px 5px 5px; +} + +.voice-settings-window > box { + padding: 5px; +} + +.voice-settings-window scale { + margin-right: 8px; +} + +.message-input scrollbar.vertical slider { + min-height: 0px; +} + +.message-text.pending { + color: alpha(currentColor, 0.5); +} + +.message-text.failed { + color: red; } diff --git a/src/abaddon.cpp b/src/abaddon.cpp index d949c28..8b8a0b2 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -192,13 +192,6 @@ int Abaddon::StartGTK() { dlg.run(); }); - m_css_low_provider = Gtk::CssProvider::create(); - m_css_low_provider->signal_parsing_error().connect([](const Glib::RefPtr §ion, const Glib::Error &error) { - Gtk::MessageDialog dlg("low-priority css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); - dlg.set_position(Gtk::WIN_POS_CENTER); - dlg.run(); - }); - #ifdef _WIN32 bool png_found = false; bool gif_found = false; @@ -1086,10 +1079,6 @@ void Abaddon::ActionReloadCSS() { Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider); m_css_provider->load_from_path(GetCSSPath("/" + GetSettings().MainCSS)); Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider); - m_css_low_provider->load_from_path(GetCSSPath("/application-low-priority.css")); - Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); } catch (Glib::Error &e) { Gtk::MessageDialog dlg(*m_main_window, "css failed to load (" + e.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dlg.set_position(Gtk::WIN_POS_CENTER); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 85b2fa0..6093523 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -182,7 +182,6 @@ private: mutable std::mutex m_mutex; Glib::RefPtr m_gtk_app; Glib::RefPtr m_css_provider; - Glib::RefPtr m_css_low_provider; // registered with a lower priority to allow better customization Glib::RefPtr m_tray; std::unique_ptr m_main_window; // wah wah cant create a gtkstylecontext fuck you diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index ac98512..bf84a9b 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -7,6 +7,49 @@ constexpr static double M_PI = 3.14159265358979; constexpr static double M_PI_H = M_PI / 2.0; constexpr static double M_PI_3_2 = M_PI * 3.0 / 2.0; +void AddUnreadIndicator(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area) { + static const auto color_setting = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor); + + const auto color = color_setting.get_alpha_u() > 0 ? color_setting : widget.get_style_context()->get_background_color(Gtk::STATE_FLAG_SELECTED); + + cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); + const auto x = background_area.get_x(); + const auto y = background_area.get_y(); + const auto w = background_area.get_width(); + const auto h = background_area.get_height(); + cr->rectangle(x, y, 3, h); + cr->fill(); +} + +void RenderExpander(int x_offset, const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, bool is_expanded) { + constexpr static int len = 5; + int x1, y1, x2, y2, x3, y3; + if (is_expanded) { + x1 = background_area.get_x() + x_offset; + y1 = background_area.get_y() + background_area.get_height() / 2 - len; + x2 = background_area.get_x() + x_offset + len; + y2 = background_area.get_y() + background_area.get_height() / 2 + len; + x3 = background_area.get_x() + x_offset + len * 2; + y3 = background_area.get_y() + background_area.get_height() / 2 - len; + } else { + x1 = background_area.get_x() + x_offset; + y1 = background_area.get_y() + background_area.get_height() / 2 - len; + x2 = background_area.get_x() + x_offset + len * 2; + y2 = background_area.get_y() + background_area.get_height() / 2; + x3 = background_area.get_x() + x_offset; + y3 = background_area.get_y() + background_area.get_height() / 2 + len; + } + cr->move_to(x1, y1); + cr->line_to(x2, y2); + cr->line_to(x3, y3); + auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelsExpanderColor); + if (expander_color.get_alpha_u() == 0) { + expander_color = widget.get_style_context()->get_background_color(Gtk::STATE_FLAG_SELECTED); + } + cr->set_source_rgb(expander_color.get_red(), expander_color.get_green(), expander_color.get_blue()); + cr->stroke(); +} + CellRendererChannels::CellRendererChannels() : Glib::ObjectBase(typeid(CellRendererChannels)) , Gtk::CellRenderer() @@ -207,30 +250,7 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc_folder(Gtk::Widg } void CellRendererChannels::render_vfunc_folder(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { - constexpr static int len = 5; - int x1, y1, x2, y2, x3, y3; - if (property_expanded()) { - x1 = background_area.get_x() + 7; - y1 = background_area.get_y() + background_area.get_height() / 2 - len; - x2 = background_area.get_x() + 7 + len; - y2 = background_area.get_y() + background_area.get_height() / 2 + len; - x3 = background_area.get_x() + 7 + len * 2; - y3 = background_area.get_y() + background_area.get_height() / 2 - len; - } else { - x1 = background_area.get_x() + 7; - y1 = background_area.get_y() + background_area.get_height() / 2 - len; - x2 = background_area.get_x() + 7 + len * 2; - y2 = background_area.get_y() + background_area.get_height() / 2; - x3 = background_area.get_x() + 7; - y3 = background_area.get_y() + background_area.get_height() / 2 + len; - } - cr->move_to(x1, y1); - cr->line_to(x2, y2); - cr->line_to(x3, y3); - const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelsExpanderColor); - cr->set_source_rgb(expander_color.get_red(), expander_color.get_green(), expander_color.get_blue()); - cr->stroke(); - + RenderExpander(7, cr, widget, background_area, property_expanded()); Gtk::Requisition text_minimum, text_natural; m_renderer_text.get_preferred_size(widget, text_minimum, text_natural); @@ -241,11 +261,8 @@ void CellRendererChannels::render_vfunc_folder(const Cairo::RefPtr(text_w), static_cast(text_h)); - static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor); - m_renderer_text.property_foreground_rgba() = color; m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); const bool hover_only = Abaddon::Get().GetSettings().AnimatedGuildHoverOnly; @@ -373,14 +388,9 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtrset_source_rgb(color.get_red(), color.get_green(), color.get_blue()); - const auto x = background_area.get_x(); - const auto y = background_area.get_y(); - const auto w = background_area.get_width(); - const auto h = background_area.get_height(); - cr->rectangle(x, y + h / 2.0 - 24.0 / 2.0, 3.0, 24.0); - cr->fill(); + auto area = background_area; + area.set_y(area.get_y() + area.get_height() / 2.0 - 24.0 / 2.0); + AddUnreadIndicator(cr, widget, area); } if (total_mentions < 1) return; @@ -410,42 +420,8 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc_category(Gtk::Wi m_renderer_text.get_preferred_height_for_width(widget, width, minimum_height, natural_height); } -void AddUnreadIndicator(const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area) { - static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor); - cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); - const auto x = background_area.get_x(); - const auto y = background_area.get_y(); - const auto w = background_area.get_width(); - const auto h = background_area.get_height(); - cr->rectangle(x, y, 3, h); - cr->fill(); -} - void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { - // todo: figure out how Gtk::Arrow is rendered because i like it better :^) - constexpr static int len = 5; - int x1, y1, x2, y2, x3, y3; - if (property_expanded()) { - x1 = background_area.get_x() + 7; - y1 = background_area.get_y() + background_area.get_height() / 2 - len; - x2 = background_area.get_x() + 7 + len; - y2 = background_area.get_y() + background_area.get_height() / 2 + len; - x3 = background_area.get_x() + 7 + len * 2; - y3 = background_area.get_y() + background_area.get_height() / 2 - len; - } else { - x1 = background_area.get_x() + 7; - y1 = background_area.get_y() + background_area.get_height() / 2 - len; - x2 = background_area.get_x() + 7 + len * 2; - y2 = background_area.get_y() + background_area.get_height() / 2; - x3 = background_area.get_x() + 7; - y3 = background_area.get_y() + background_area.get_height() / 2 + len; - } - cr->move_to(x1, y1); - cr->line_to(x2, y2); - cr->line_to(x3, y3); - const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelsExpanderColor); - cr->set_source_rgb(expander_color.get_red(), expander_color.get_green(), expander_color.get_blue()); - cr->stroke(); + RenderExpander(7, cr, widget, background_area, property_expanded()); Gtk::Requisition text_minimum, text_natural; m_renderer_text.get_preferred_size(widget, text_minimum, text_natural); @@ -457,23 +433,14 @@ void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr 0) { - AddUnreadIndicator(cr, background_area); + AddUnreadIndicator(cr, widget, background_area); } - m_renderer_text.property_foreground_rgba() = color; } m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); - m_renderer_text.property_foreground_set() = false; } // text channel @@ -509,23 +476,14 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtrget_color(Gtk::STATE_FLAG_NORMAL); + if (property_nsfw()) color = nsfw_color; + if (is_muted) color.set_alpha(0.6); + + m_renderer_text.property_foreground_rgba() = color; m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); - // unset foreground to default so properties dont bleed m_renderer_text.property_foreground_set() = false; // unread @@ -535,7 +493,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtrset_source_rgb(color.get_red(), color.get_green(), color.get_blue()); - const auto x = background_area.get_x(); - const auto y = background_area.get_y(); - const auto w = background_area.get_width(); - const auto h = background_area.get_height(); - cr->rectangle(x, y, 3, h); - cr->fill(); + AddUnreadIndicator(cr, widget, background_area); } if (unread_state < 1) return; @@ -667,31 +607,7 @@ void CellRendererChannels::render_vfunc_voice_channel(const Cairo::RefPtrshow_in_cairo_context(cr); - // expander - constexpr static int len = 5; - constexpr static int offset = 24; - int x1, y1, x2, y2, x3, y3; - if (property_expanded()) { - x1 = background_area.get_x() + offset; - y1 = background_area.get_y() + background_area.get_height() / 2 - len; - x2 = background_area.get_x() + offset + len; - y2 = background_area.get_y() + background_area.get_height() / 2 + len; - x3 = background_area.get_x() + offset + len * 2; - y3 = background_area.get_y() + background_area.get_height() / 2 - len; - } else { - x1 = background_area.get_x() + offset; - y1 = background_area.get_y() + background_area.get_height() / 2 - len; - x2 = background_area.get_x() + offset + len * 2; - y2 = background_area.get_y() + background_area.get_height() / 2; - x3 = background_area.get_x() + offset; - y3 = background_area.get_y() + background_area.get_height() / 2 + len; - } - cr->move_to(x1, y1); - cr->line_to(x2, y2); - cr->line_to(x3, y3); - const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelsExpanderColor); - cr->set_source_rgb(expander_color.get_red(), expander_color.get_green(), expander_color.get_blue()); - cr->stroke(); + RenderExpander(24, cr, widget, background_area, property_expanded()); } // voice participant @@ -897,18 +813,7 @@ void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr & const auto id = m_property_id.get_value(); const bool is_muted = discord.IsChannelMuted(id); - static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelColor); - if (Abaddon::Get().GetDiscordClient().IsChannelMuted(m_property_id.get_value())) { - auto muted = color; - muted.set_red(muted.get_red() * 0.5); - muted.set_green(muted.get_green() * 0.5); - muted.set_blue(muted.get_blue() * 0.5); - m_renderer_text.property_foreground_rgba() = muted; - } else { - m_renderer_text.property_foreground_rgba() = color; - } m_renderer_text.render(cr, widget, background_area, text_cell_area, flags); - m_renderer_text.property_foreground_set() = false; Gdk::Cairo::set_source_pixbuf(cr, m_property_pixbuf.get_value(), icon_x, icon_y); cr->rectangle(icon_x, icon_y, icon_w, icon_h); @@ -921,14 +826,7 @@ void CellRendererChannels::render_vfunc_dm(const Cairo::RefPtr & if (unread_state < 0) return; if (!is_muted) { - static const auto color = Gdk::RGBA(Abaddon::Get().GetSettings().UnreadIndicatorColor); - cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); - const auto x = background_area.get_x(); - const auto y = background_area.get_y(); - const auto w = background_area.get_width(); - const auto h = background_area.get_height(); - cr->rectangle(x, y, 3, h); - cr->fill(); + AddUnreadIndicator(cr, widget, background_area); } } @@ -955,8 +853,11 @@ void CellRendererChannels::unread_render_mentions(const Cairo::RefPtrget_pixel_size(width, height); { - static const auto bg = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeColor); - static const auto text = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeTextColor); + static const auto badge_setting = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeColor); + static const auto text_setting = Gdk::RGBA(Abaddon::Get().GetSettings().MentionBadgeTextColor); + + auto bg = badge_setting.get_alpha_u() > 0 ? badge_setting : widget.get_style_context()->get_background_color(Gtk::STATE_FLAG_SELECTED); + auto text = text_setting.get_alpha_u() > 0 ? text_setting : widget.get_style_context()->get_color(Gtk::STATE_FLAG_SELECTED); const auto x = cell_area.get_x() + edge - width - MentionsRightPad; const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0 - 1; diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 10896fb..1db03ed 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -20,7 +20,7 @@ ChatInputText::ChatInputText() { m_textview.signal_key_press_event().connect(cb, false); m_textview.set_hexpand(false); m_textview.set_halign(Gtk::ALIGN_FILL); - m_textview.set_valign(Gtk::ALIGN_CENTER); + m_textview.set_valign(Gtk::ALIGN_FILL); m_textview.set_wrap_mode(Gtk::WRAP_WORD_CHAR); m_textview.show(); add(m_textview); @@ -113,30 +113,25 @@ ChatInputTextContainer::ChatInputTextContainer() { }; m_input.signal_key_press_proxy().connect(cb); + m_upload_button.set_image(m_upload_img); + m_upload_button.set_halign(Gtk::ALIGN_CENTER); + m_upload_button.set_valign(Gtk::ALIGN_CENTER); + m_upload_button.get_style_context()->add_class(GTK_STYLE_CLASS_FLAT); + m_upload_img.property_icon_name() = "document-send-symbolic"; m_upload_img.property_icon_size() = Gtk::ICON_SIZE_LARGE_TOOLBAR; m_upload_img.get_style_context()->add_class("message-input-browse-icon"); - AddPointerCursor(m_upload_ev); - - m_upload_ev.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool { - if (ev->button == GDK_BUTTON_PRIMARY) { - ShowFileChooser(); - // return focus - m_input.grab_focus(); - return true; - } - return false; + m_upload_button.signal_clicked().connect([this]() { + ShowFileChooser(); + m_input.grab_focus(); }); - m_upload_ev.add(m_upload_img); - add_overlay(m_upload_ev); - add(m_input); + m_upload_box.pack_start(m_upload_button); + pack_start(m_upload_box, false, false); + pack_start(m_input); show_all_children(); - - // stop the overlay from using (start) padding - signal_get_child_position().connect(sigc::mem_fun(*this, &ChatInputTextContainer::GetChildPosition), false); } void ChatInputTextContainer::ShowFileChooser() { @@ -160,39 +155,11 @@ ChatInputText &ChatInputTextContainer::Get() { } void ChatInputTextContainer::ShowChooserIcon() { - m_upload_ev.show(); + m_upload_button.show(); } void ChatInputTextContainer::HideChooserIcon() { - m_upload_ev.hide(); -} - -bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos) { - Gtk::Allocation main_alloc; - { - auto *grandchild = m_input.get_child(); - int x, y; - if (grandchild->translate_coordinates(m_input, 0, 0, x, y)) { - main_alloc.set_x(x); - main_alloc.set_y(y); - } else { - main_alloc.set_x(0); - main_alloc.set_y(0); - } - main_alloc.set_width(grandchild->get_allocated_width()); - main_alloc.set_height(grandchild->get_allocated_height()); - } - - Gtk::Requisition min, req; - child->get_preferred_size(min, req); - - // let css move it around - pos.set_x(0); - pos.set_y(0); - pos.set_width(std::max(min.width, std::min(main_alloc.get_width(), req.width))); - pos.set_height(std::max(min.height, std::min(main_alloc.get_height(), req.height))); - - return true; + m_upload_button.hide(); } ChatInputTextContainer::type_signal_add_attachment ChatInputTextContainer::signal_add_attachment() { diff --git a/src/components/chatinput.hpp b/src/components/chatinput.hpp index be8c141..231d67c 100644 --- a/src/components/chatinput.hpp +++ b/src/components/chatinput.hpp @@ -104,7 +104,7 @@ private: }; // file upload, text -class ChatInputTextContainer : public Gtk::Overlay { +class ChatInputTextContainer : public Gtk::Box { public: ChatInputTextContainer(); @@ -116,9 +116,9 @@ public: private: void ShowFileChooser(); - bool GetChildPosition(Gtk::Widget *child, Gdk::Rectangle &pos); - Gtk::EventBox m_upload_ev; + Gtk::Box m_upload_box; + Gtk::Button m_upload_button; Gtk::Image m_upload_img; ChatInputText m_input; diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 2c2f9dd..a503294 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -364,7 +364,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb } return false; }); - static auto color = Abaddon::Get().GetSettings().LinkColor; + const auto color = title_label->get_style_context()->get_color(Gtk::STATE_FLAG_LINK); title_label->override_color(Gdk::RGBA(color)); title_label->set_markup("" + Glib::Markup::escape_text(*embed.Title) + ""); } @@ -856,7 +856,8 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) { std::string link = match.fetch(0); auto tag = buf->create_tag(); m_link_tagmap[tag] = link; - tag->property_foreground_rgba() = Gdk::RGBA(Abaddon::Get().GetSettings().LinkColor); + const auto color = tv.get_style_context()->get_color(Gtk::STATE_FLAG_LINK); + tag->property_foreground_rgba() = color; tag->set_property("underline", 1); // stupid workaround for vcpkg bug (i think) const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart); diff --git a/src/components/chatwindow.cpp b/src/components/chatwindow.cpp index aeed4ed..9a2493d 100644 --- a/src/components/chatwindow.cpp +++ b/src/components/chatwindow.cpp @@ -5,7 +5,7 @@ #include "chatlist.hpp" #include "constants.hpp" #ifdef WITH_LIBHANDY - #include "channeltabswitcherhandy.hpp" +#include "channeltabswitcherhandy.hpp" #endif ChatWindow::ChatWindow() { @@ -109,7 +109,13 @@ ChatWindow::ChatWindow() { m_main->add(*m_meta); m_main->add(m_progress); - m_progress.show(); + m_progress.signal_start().connect([this]() { + m_progress.show(); + }); + + m_progress.signal_stop().connect([this]() { + m_progress.hide(); + }); m_main->show(); } diff --git a/src/components/progressbar.cpp b/src/components/progressbar.cpp index 75f86bb..65abfae 100644 --- a/src/components/progressbar.cpp +++ b/src/components/progressbar.cpp @@ -5,12 +5,19 @@ MessageUploadProgressBar::MessageUploadProgressBar() { auto &discord = Abaddon::Get().GetDiscordClient(); discord.signal_message_progress().connect([this](const std::string &nonce, float percent) { if (nonce == m_last_nonce) { + if (!m_active) { + m_active = true; + m_signal_start.emit(); + } set_fraction(percent); } }); discord.signal_message_send_fail().connect([this](const std::string &nonce, float) { - if (nonce == m_last_nonce) + if (nonce == m_last_nonce) { set_fraction(0.0); + m_active = false; + m_signal_stop.emit(); + } }); discord.signal_message_create().connect([this](const Message &msg) { if (msg.IsPending) { @@ -18,6 +25,16 @@ MessageUploadProgressBar::MessageUploadProgressBar() { } else if (msg.Nonce.has_value() && (*msg.Nonce == m_last_nonce)) { m_last_nonce = ""; set_fraction(0.0); + m_active = false; + m_signal_stop.emit(); } }); } + +MessageUploadProgressBar::type_signal_start MessageUploadProgressBar::signal_start() { + return m_signal_start; +} + +MessageUploadProgressBar::type_signal_stop MessageUploadProgressBar::signal_stop() { + return m_signal_stop; +} diff --git a/src/components/progressbar.hpp b/src/components/progressbar.hpp index 483ee47..8efb87a 100644 --- a/src/components/progressbar.hpp +++ b/src/components/progressbar.hpp @@ -6,5 +6,16 @@ public: MessageUploadProgressBar(); private: + bool m_active = false; std::string m_last_nonce; + + using type_signal_start = sigc::signal; + using type_signal_stop = sigc::signal; + + type_signal_start m_signal_start; + type_signal_stop m_signal_stop; + +public: + type_signal_start signal_start(); + type_signal_stop signal_stop(); }; diff --git a/src/dialogs/setstatus.cpp b/src/dialogs/setstatus.cpp index 7a3a038..24f0b29 100644 --- a/src/dialogs/setstatus.cpp +++ b/src/dialogs/setstatus.cpp @@ -1,17 +1,31 @@ #include "setstatus.hpp" +static const std::array feelings = { + "wonderful", + "splendiferous", + "delicious", + "outstanding", + "amazing", + "great", + "marvelous", + "superb", + "out of this world", + "stupendous", + "tip-top", + "horrible", +}; + SetStatusDialog::SetStatusDialog(Gtk::Window &parent) : Gtk::Dialog("Set Status", parent, true) , m_layout(Gtk::ORIENTATION_VERTICAL) - , m_bottom(Gtk::ORIENTATION_HORIZONTAL) , m_ok("OK") - , m_cancel("Cancel") - , m_bbox(Gtk::ORIENTATION_HORIZONTAL) { - set_default_size(300, 50); + , m_cancel("Cancel") { + set_default_size(350, 200); get_style_context()->add_class("app-window"); get_style_context()->add_class("app-popup"); + get_style_context()->add_class("set-status-dialog"); - m_text.set_placeholder_text("Status text"); + m_text.set_placeholder_text("I feel " + Glib::ustring(feelings[rand() % feelings.size()]) + "!"); m_status_combo.append("online", "Online"); m_status_combo.append("dnd", "Do Not Disturb"); @@ -35,16 +49,17 @@ SetStatusDialog::SetStatusDialog(Gtk::Window &parent) response(Gtk::RESPONSE_CANCEL); }); - m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK); - m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK); - m_bbox.set_layout(Gtk::BUTTONBOX_END); + m_layout.pack_start(*Gtk::make_managed("How are you, " + Abaddon::Get().GetDiscordClient().GetUserData().GetDisplayName() + "?", Gtk::ALIGN_START)); + m_layout.pack_start(m_text); + m_layout.pack_start(*Gtk::make_managed("Status", Gtk::ALIGN_START)); + m_layout.pack_start(m_status_combo); + m_layout.pack_start(*Gtk::make_managed("Activity", Gtk::ALIGN_START)); + m_layout.pack_start(m_type_combo); - m_bottom.add(m_status_combo); - m_bottom.add(m_type_combo); - m_bottom.add(m_bbox); - m_layout.add(m_text); - m_layout.add(m_bottom); get_content_area()->add(m_layout); + get_action_area()->pack_start(m_ok, Gtk::PACK_SHRINK); + get_action_area()->pack_start(m_cancel, Gtk::PACK_SHRINK); + get_action_area()->set_layout(Gtk::BUTTONBOX_START); show_all_children(); } diff --git a/src/dialogs/setstatus.hpp b/src/dialogs/setstatus.hpp index b0e6a4c..d4a015d 100644 --- a/src/dialogs/setstatus.hpp +++ b/src/dialogs/setstatus.hpp @@ -8,14 +8,12 @@ public: PresenceStatus GetStatusType() const; std::string GetActivityName() const; -protected: +private: Gtk::Box m_layout; - Gtk::Box m_bottom; Gtk::Entry m_text; Gtk::ComboBoxText m_status_combo; Gtk::ComboBoxText m_type_combo; Gtk::Button m_ok; Gtk::Button m_cancel; - Gtk::ButtonBox m_bbox; }; diff --git a/src/settings.cpp b/src/settings.cpp index c824a34..d1dff3f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -68,9 +68,7 @@ void SettingsManager::ReadSettings() { SMINT("http", "concurrent", CacheHTTPConcurrency); SMSTR("http", "user_agent", UserAgent); SMSTR("style", "expandercolor", ChannelsExpanderColor); - SMSTR("style", "linkcolor", LinkColor); SMSTR("style", "nsfwchannelcolor", NSFWChannelColor); - SMSTR("style", "channelcolor", ChannelColor); SMSTR("style", "mentionbadgecolor", MentionBadgeColor); SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor); SMSTR("style", "unreadcolor", UnreadIndicatorColor); @@ -159,9 +157,7 @@ void SettingsManager::Close() { SMINT("http", "concurrent", CacheHTTPConcurrency); SMSTR("http", "user_agent", UserAgent); SMSTR("style", "expandercolor", ChannelsExpanderColor); - SMSTR("style", "linkcolor", LinkColor); SMSTR("style", "nsfwchannelcolor", NSFWChannelColor); - SMSTR("style", "channelcolor", ChannelColor); SMSTR("style", "mentionbadgecolor", MentionBadgeColor); SMSTR("style", "mentionbadgetextcolor", MentionBadgeTextColor); SMSTR("style", "unreadcolor", UnreadIndicatorColor); diff --git a/src/settings.hpp b/src/settings.hpp index 037233b..419734c 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -38,14 +38,11 @@ public: std::string UserAgent { "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" }; // [style] - // TODO: convert to StyleProperty... or maybe not? i still cant figure out what the "correct" method is for this - std::string LinkColor { "rgba(40, 200, 180, 255)" }; - std::string ChannelsExpanderColor { "rgba(255, 83, 112, 255)" }; - std::string NSFWChannelColor { "#ed6666" }; - std::string ChannelColor { "#fbfbfb" }; - std::string MentionBadgeColor { "#b82525" }; - std::string MentionBadgeTextColor { "#fbfbfb" }; - std::string UnreadIndicatorColor { "#ffffff" }; + std::string ChannelsExpanderColor { "rgba(255, 83, 112, 0)" }; + std::string NSFWChannelColor { "#970d0d" }; + std::string MentionBadgeColor { "rgba(184, 37, 37, 0)" }; + std::string MentionBadgeTextColor { "rgba(251, 251, 251, 0)" }; + std::string UnreadIndicatorColor { "rgba(255, 255, 255, 0)" }; // [notifications] #ifdef _WIN32 diff --git a/src/windows/voicesettingswindow.cpp b/src/windows/voicesettingswindow.cpp index 97f77b5..1a112f5 100644 --- a/src/windows/voicesettingswindow.cpp +++ b/src/windows/voicesettingswindow.cpp @@ -12,6 +12,7 @@ VoiceSettingsWindow::VoiceSettingsWindow() : m_main(Gtk::ORIENTATION_VERTICAL) { get_style_context()->add_class("app-window"); + get_style_context()->add_class("voice-settings-window"); set_default_size(300, 300); m_encoding_mode.append("Voice"); @@ -115,10 +116,21 @@ VoiceSettingsWindow::VoiceSettingsWindow() m_signal_gain.emit(m_gain.get_value() / 100.0); }); - m_main.add(m_encoding_mode); - m_main.add(m_signal); - m_main.add(m_bitrate); - m_main.add(m_gain); + auto *layout = Gtk::make_managed(); + auto *labels = Gtk::make_managed(); + auto *widgets = Gtk::make_managed(); + layout->pack_start(*labels, false, true, 5); + layout->pack_start(*widgets); + labels->pack_start(*Gtk::make_managed("Coding Mode", Gtk::ALIGN_END)); + labels->pack_start(*Gtk::make_managed("Signal Hint", Gtk::ALIGN_END)); + labels->pack_start(*Gtk::make_managed("Bitrate", Gtk::ALIGN_END)); + labels->pack_start(*Gtk::make_managed("Gain", Gtk::ALIGN_END)); + widgets->pack_start(m_encoding_mode); + widgets->pack_start(m_signal); + widgets->pack_start(m_bitrate); + widgets->pack_start(m_gain); + + m_main.add(*layout); add(m_main); show_all_children(); diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 16a9fed..825681c 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -181,7 +181,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_mix_mono.set_active(audio.GetMixMono()); m_mix_mono.signal_toggled().connect([this]() { - Abaddon::Get().GetAudio().SetMixMono(m_mix_mono.get_active()); + Abaddon::Get().GetAudio().SetMixMono(m_mix_mono.get_active()); }); auto *playback_renderer = Gtk::make_managed(); @@ -225,20 +225,40 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) window->show(); }); + auto *sliders_container = Gtk::make_managed(); + auto *sliders_labels = Gtk::make_managed(); + auto *sliders_sliders = Gtk::make_managed(); + sliders_container->pack_start(*sliders_labels, false, true, 2); + sliders_container->pack_start(*sliders_sliders); + sliders_labels->pack_start(*Gtk::make_managed("Threshold", Gtk::ALIGN_END)); + sliders_labels->pack_start(*Gtk::make_managed("Gain", Gtk::ALIGN_END)); + sliders_sliders->pack_start(m_vad_param); + sliders_sliders->pack_start(m_capture_gain); + + auto *combos_container = Gtk::make_managed(); + auto *combos_labels = Gtk::make_managed(); + auto *combos_combos = Gtk::make_managed(); + combos_container->pack_start(*combos_labels, false, true, 6); + combos_container->pack_start(*combos_combos, Gtk::PACK_EXPAND_WIDGET, 6); + combos_labels->pack_start(*Gtk::make_managed("VAD Method", Gtk::ALIGN_END)); + combos_labels->pack_start(*Gtk::make_managed("Output Device", Gtk::ALIGN_END)); + combos_labels->pack_start(*Gtk::make_managed("Input Device", Gtk::ALIGN_END)); + combos_combos->pack_start(m_vad_combo); + combos_combos->pack_start(m_playback_combo); + combos_combos->pack_start(m_capture_combo); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); m_controls.add(m_noise_suppression); m_controls.add(m_mix_mono); - m_main.add(m_menu_bar); - m_main.add(m_controls); - m_main.add(m_vad_value); - m_main.add(m_vad_param); - m_main.add(m_capture_gain); - m_main.add(m_scroll); - m_main.add(m_vad_combo); - m_main.add(m_playback_combo); - m_main.add(m_capture_combo); + m_main.pack_start(m_menu_bar); + m_main.pack_start(m_controls); + m_main.pack_start(m_vad_value); + m_main.pack_start(*Gtk::make_managed("Input Settings")); + m_main.pack_start(*sliders_container); + m_main.pack_start(m_scroll); + m_main.pack_start(*combos_container, Gtk::PACK_EXPAND_WIDGET, 2); add(m_main); show_all_children();