mirror of
https://github.com/celisej567/abaddon.git
synced 2026-09-04 11:33:27 +03:00
add ability to reorder roles
- some other small tweaks
This commit is contained in:
141
components/draglistbox.cpp
Normal file
141
components/draglistbox.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "draglistbox.hpp"
|
||||
|
||||
DragListBox::DragListBox() {
|
||||
drag_dest_set(m_entries, Gtk::DEST_DEFAULT_MOTION | Gtk::DEST_DEFAULT_DROP, Gdk::ACTION_MOVE);
|
||||
}
|
||||
|
||||
void DragListBox::row_drag_begin(Gtk::Widget *widget, const Glib::RefPtr<Gdk::DragContext> &context) {
|
||||
m_drag_row = dynamic_cast<Gtk::ListBoxRow *>(widget->get_ancestor(GTK_TYPE_LIST_BOX_ROW));
|
||||
|
||||
auto alloc = m_drag_row->get_allocation();
|
||||
auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, alloc.get_width(), alloc.get_height());
|
||||
auto cr = Cairo::Context::create(surface);
|
||||
|
||||
m_drag_row->get_style_context()->add_class("drag-icon");
|
||||
gtk_widget_draw(reinterpret_cast<GtkWidget *>(m_drag_row->gobj()), cr->cobj());
|
||||
m_drag_row->get_style_context()->remove_class("drag-icon");
|
||||
|
||||
int x, y;
|
||||
widget->translate_coordinates(*m_drag_row, 0, 0, x, y);
|
||||
surface->set_device_offset(-x, -y);
|
||||
context->set_icon(surface);
|
||||
}
|
||||
|
||||
bool DragListBox::on_drag_motion(const Glib::RefPtr<Gdk::DragContext> &context, gint x, gint y, guint time) {
|
||||
if (y > m_hover_top || y < m_hover_bottom) {
|
||||
auto *row = get_row_at_y(y);
|
||||
if (row == nullptr) return true;
|
||||
const bool old_top = m_top;
|
||||
const auto alloc = row->get_allocation();
|
||||
|
||||
const int hover_row_y = alloc.get_y();
|
||||
const int hover_row_height = alloc.get_height();
|
||||
if (row != m_drag_row) {
|
||||
if (y < hover_row_y + hover_row_height / 2) {
|
||||
m_hover_top = hover_row_y;
|
||||
m_hover_bottom = m_hover_top + hover_row_height / 2;
|
||||
row->get_style_context()->add_class("drag-hover-top");
|
||||
row->get_style_context()->remove_class("drag-hover-bottom");
|
||||
m_top = true;
|
||||
} else {
|
||||
m_hover_top = hover_row_y + hover_row_height / 2;
|
||||
m_hover_bottom = hover_row_y + hover_row_height;
|
||||
row->get_style_context()->add_class("drag-hover-bottom");
|
||||
row->get_style_context()->remove_class("drag-hover-top");
|
||||
m_top = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_hover_row != nullptr && m_hover_row != row) {
|
||||
if (old_top)
|
||||
m_hover_row->get_style_context()->remove_class("drag-hover-top");
|
||||
else
|
||||
m_hover_row->get_style_context()->remove_class("drag-hover-bottom");
|
||||
}
|
||||
|
||||
m_hover_row = row;
|
||||
}
|
||||
|
||||
check_scroll(y);
|
||||
if (m_should_scroll && !m_scrolling) {
|
||||
m_scrolling = true;
|
||||
Glib::signal_timeout().connect(sigc::mem_fun(*this, &DragListBox::scroll), SCROLL_DELAY);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DragListBox::on_drag_leave(const Glib::RefPtr<Gdk::DragContext> &context, guint time) {
|
||||
m_should_scroll = false;
|
||||
}
|
||||
|
||||
void DragListBox::check_scroll(gint y) {
|
||||
if (!get_focus_vadjustment())
|
||||
return;
|
||||
|
||||
const double vadjustment_min = get_focus_vadjustment()->get_value();
|
||||
const double vadjustment_max = get_focus_vadjustment()->get_page_size() + vadjustment_min;
|
||||
const double show_min = std::max(0, y - SCROLL_DISTANCE);
|
||||
const double show_max = std::min(get_focus_vadjustment()->get_upper(), static_cast<double>(y) + SCROLL_DISTANCE);
|
||||
if (vadjustment_min > show_min) {
|
||||
m_should_scroll = true;
|
||||
m_scroll_up = true;
|
||||
} else if (vadjustment_max < show_max) {
|
||||
m_should_scroll = true;
|
||||
m_scroll_up = false;
|
||||
} else {
|
||||
m_should_scroll = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DragListBox::scroll() {
|
||||
if (m_should_scroll) {
|
||||
if (m_scroll_up) {
|
||||
get_focus_vadjustment()->set_value(get_focus_vadjustment()->get_value() - SCROLL_STEP_SIZE);
|
||||
} else {
|
||||
get_focus_vadjustment()->set_value(get_focus_vadjustment()->get_value() + SCROLL_STEP_SIZE);
|
||||
}
|
||||
} else {
|
||||
m_scrolling = false;
|
||||
}
|
||||
return m_should_scroll;
|
||||
}
|
||||
|
||||
void DragListBox::on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y, const Gtk::SelectionData &selection_data, guint info, guint time) {
|
||||
int index = 0;
|
||||
if (m_hover_row != nullptr) {
|
||||
if (m_top) {
|
||||
index = m_hover_row->get_index() - 1;
|
||||
m_hover_row->get_style_context()->remove_class("drag-hover-top");
|
||||
} else {
|
||||
index = m_hover_row->get_index();
|
||||
m_hover_row->get_style_context()->remove_class("drag-hover-bottom");
|
||||
}
|
||||
|
||||
Gtk::Widget *handle = *reinterpret_cast<Gtk::Widget *const *>(selection_data.get_data());
|
||||
auto *row = dynamic_cast<Gtk::ListBoxRow *>(handle->get_ancestor(GTK_TYPE_LIST_BOX_ROW));
|
||||
|
||||
if (row != nullptr && row != m_hover_row) {
|
||||
if (row->get_index() > index)
|
||||
index += 1;
|
||||
if (m_signal_on_drop.emit(row, index)) return;
|
||||
row->get_parent()->remove(*row);
|
||||
insert(*row, index);
|
||||
}
|
||||
}
|
||||
|
||||
m_drag_row = nullptr;
|
||||
}
|
||||
|
||||
void DragListBox::add_draggable(Gtk::ListBoxRow *widget) {
|
||||
widget->drag_source_set(m_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
|
||||
widget->signal_drag_begin().connect(sigc::bind<0>(sigc::mem_fun(*this, &DragListBox::row_drag_begin), widget));
|
||||
widget->signal_drag_data_get().connect([this, widget](const Glib::RefPtr<Gdk::DragContext> &context, Gtk::SelectionData &selection_data, guint info, guint time) {
|
||||
selection_data.set("GTK_LIST_BOX_ROW", 32, reinterpret_cast<const guint8 *>(&widget), sizeof(&widget));
|
||||
});
|
||||
add(*widget);
|
||||
}
|
||||
|
||||
DragListBox::type_signal_on_drop DragListBox::signal_on_drop() {
|
||||
return m_signal_on_drop;
|
||||
}
|
||||
45
components/draglistbox.hpp
Normal file
45
components/draglistbox.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
|
||||
class DragListBox : public Gtk::ListBox {
|
||||
public:
|
||||
DragListBox();
|
||||
|
||||
void row_drag_begin(Gtk::Widget *widget, const Glib::RefPtr<Gdk::DragContext> &context);
|
||||
|
||||
bool on_drag_motion(const Glib::RefPtr<Gdk::DragContext> &context, gint x, gint y, guint time) override;
|
||||
|
||||
void on_drag_leave(const Glib::RefPtr<Gdk::DragContext> &context, guint time) override;
|
||||
|
||||
void check_scroll(gint y);
|
||||
|
||||
bool scroll();
|
||||
|
||||
void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y, const Gtk::SelectionData &selection_data, guint info, guint time) override;
|
||||
|
||||
void add_draggable(Gtk::ListBoxRow *widget);
|
||||
|
||||
private:
|
||||
Gtk::ListBoxRow *m_hover_row = nullptr;
|
||||
Gtk::ListBoxRow *m_drag_row = nullptr;
|
||||
bool m_top = false;
|
||||
int m_hover_top = 0;
|
||||
int m_hover_bottom = 0;
|
||||
bool m_should_scroll = false;
|
||||
bool m_scrolling = false;
|
||||
bool m_scroll_up = false;
|
||||
|
||||
constexpr static int SCROLL_STEP_SIZE = 8;
|
||||
constexpr static int SCROLL_DISTANCE = 30;
|
||||
constexpr static int SCROLL_DELAY = 50;
|
||||
|
||||
const std::vector<Gtk::TargetEntry> m_entries = {
|
||||
Gtk::TargetEntry("GTK_LIST_BOX_ROW", Gtk::TARGET_SAME_APP, 0),
|
||||
};
|
||||
|
||||
using type_signal_on_drop = sigc::signal<bool, Gtk::ListBoxRow *, int /* new index */>;
|
||||
type_signal_on_drop m_signal_on_drop;
|
||||
|
||||
public:
|
||||
type_signal_on_drop signal_on_drop(); // return true to prevent drop
|
||||
};
|
||||
@@ -321,3 +321,11 @@
|
||||
.app-window colorswatch {
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.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%);
|
||||
}
|
||||
|
||||
@@ -571,6 +571,58 @@ void DiscordClient::ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::
|
||||
ModifyRoleColor(guild_id, role_id, int_color, callback);
|
||||
}
|
||||
|
||||
void DiscordClient::ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(bool success)> callback) {
|
||||
const auto roles = GetGuild(guild_id)->FetchRoles();
|
||||
if (position > roles.size()) return;
|
||||
// gay and makes you send every role in between new and old position
|
||||
size_t index_from = -1, index_to = -1;
|
||||
for (size_t i = 0; i < roles.size(); i++) {
|
||||
const auto &role = roles[i];
|
||||
if (role.ID == role_id)
|
||||
index_from = i;
|
||||
else if (role.Position == position)
|
||||
index_to = i;
|
||||
if (index_from != -1 && index_to != -1) break;
|
||||
}
|
||||
|
||||
if (index_from == -1 || index_to == -1) return;
|
||||
|
||||
int dir;
|
||||
size_t range_from, range_to;
|
||||
if (index_to > index_from) {
|
||||
dir = 1;
|
||||
range_from = index_from + 1;
|
||||
range_to = index_to + 1;
|
||||
} else {
|
||||
dir = -1;
|
||||
range_from = index_to;
|
||||
range_to = index_from;
|
||||
}
|
||||
|
||||
ModifyGuildRolePositionsObject obj;
|
||||
|
||||
obj.Positions.push_back({ roles[index_from].ID, position });
|
||||
for (size_t i = range_from; i < range_to; i++)
|
||||
obj.Positions.push_back({ roles[i].ID, roles[i].Position + dir });
|
||||
|
||||
m_http.MakePATCH("/guilds/" + std::to_string(guild_id) + "/roles", nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) {
|
||||
callback(CheckCode(response));
|
||||
});
|
||||
}
|
||||
|
||||
bool DiscordClient::CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const {
|
||||
const auto guild = *GetGuild(guild_id);
|
||||
if (guild.OwnerID == user_id) return true;
|
||||
const auto role = *GetRole(role_id);
|
||||
const auto has_modify = HasGuildPermission(user_id, guild_id, Permission::MANAGE_CHANNELS);
|
||||
const auto highest = GetMemberHighestRole(guild_id, user_id);
|
||||
return has_modify && highest.has_value() && highest->Position > role.Position;
|
||||
}
|
||||
|
||||
bool DiscordClient::CanModifyRole(Snowflake guild_id, Snowflake role_id) const {
|
||||
return CanModifyRole(guild_id, role_id, GetUserData().ID);
|
||||
}
|
||||
|
||||
std::vector<BanData> DiscordClient::GetBansInGuild(Snowflake guild_id) {
|
||||
return m_store.GetBans(guild_id);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,10 @@ public:
|
||||
void ModifyRoleName(Snowflake guild_id, Snowflake role_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(bool success)> callback);
|
||||
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const;
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const;
|
||||
|
||||
// real client doesn't seem to use the single role endpoints so neither do we
|
||||
template<typename Iter>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "httpclient.hpp"
|
||||
#include "httpclient.hpp"
|
||||
|
||||
//#define USE_LOCAL_PROXY
|
||||
HTTPClient::HTTPClient(std::string api_base)
|
||||
|
||||
@@ -376,3 +376,12 @@ void to_json(nlohmann::json &j, const ModifyGuildRoleObject &m) {
|
||||
if (m.Permissions.has_value())
|
||||
j["permissions"] = std::to_string(static_cast<uint64_t>(*m.Permissions));
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject::PositionParam &m) {
|
||||
j["id"] = m.ID;
|
||||
JS_IF("position", m.Position);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject &m) {
|
||||
j = m.Positions;
|
||||
}
|
||||
|
||||
@@ -526,3 +526,15 @@ struct ModifyGuildRoleObject {
|
||||
|
||||
friend void to_json(nlohmann::json &j, const ModifyGuildRoleObject &m);
|
||||
};
|
||||
|
||||
struct ModifyGuildRolePositionsObject {
|
||||
struct PositionParam {
|
||||
Snowflake ID;
|
||||
std::optional<int> Position; // no idea why this can be optional
|
||||
|
||||
friend void to_json(nlohmann::json &j, const PositionParam &m);
|
||||
};
|
||||
std::vector<PositionParam> Positions;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const ModifyGuildRolePositionsObject &m);
|
||||
};
|
||||
|
||||
@@ -28,23 +28,13 @@ void GuildSettingsRolesPane::OnRoleSelect(Snowflake role_id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto role = *discord.GetRole(role_id);
|
||||
m_roles_perms.SetRole(role);
|
||||
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
|
||||
const auto is_owner = discord.GetGuild(GuildID)->OwnerID == self_id;
|
||||
if (is_owner)
|
||||
m_roles_perms.set_sensitive(true);
|
||||
else {
|
||||
const bool can_modify = discord.HasGuildPermission(self_id, GuildID, Permission::MANAGE_ROLES);
|
||||
if (!can_modify) {
|
||||
m_roles_perms.set_sensitive(false);
|
||||
} else {
|
||||
const auto highest = discord.GetMemberHighestRole(GuildID, self_id);
|
||||
m_roles_perms.set_sensitive(highest.has_value() && highest->Position > role.Position);
|
||||
}
|
||||
}
|
||||
m_roles_perms.set_sensitive(discord.CanModifyRole(GuildID, role_id));
|
||||
}
|
||||
|
||||
static std::vector<Gtk::TargetEntry> g_target_entries = {
|
||||
Gtk::TargetEntry("GTK_LIST_ROLES_ROW", Gtk::TARGET_SAME_APP, 0)
|
||||
};
|
||||
|
||||
GuildSettingsRolesPaneRoles::GuildSettingsRolesPaneRoles(Snowflake guild_id)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, GuildID(guild_id) {
|
||||
@@ -61,19 +51,47 @@ GuildSettingsRolesPaneRoles::GuildSettingsRolesPaneRoles(Snowflake guild_id)
|
||||
m_signal_role_select.emit(selected->RoleID);
|
||||
});
|
||||
|
||||
m_list.set_focus_vadjustment(m_list_scroll.get_vadjustment());
|
||||
m_list.signal_on_drop().connect([this](Gtk::ListBoxRow *row_, int new_index) -> bool {
|
||||
if (auto *row = dynamic_cast<GuildSettingsRolesPaneRolesListItem *>(row_)) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto num_rows = m_list.get_children().size();
|
||||
const auto new_pos = num_rows - new_index - 1;
|
||||
if (row->RoleID == GuildID) return true; // moving role @everyone
|
||||
if (new_index == num_rows) return true; // trying to move row below @everyone
|
||||
// make sure it wont modify a neighbor role u dont have perms to modify
|
||||
if (!discord.CanModifyRole(GuildID, row->RoleID)) return false;
|
||||
const auto cb = [this](bool success) {
|
||||
if (!success) {
|
||||
Gtk::MessageDialog dlg("Failed to set role position", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
discord.ModifyRolePosition(GuildID, row->RoleID, new_pos, sigc::track_obj(cb, *this));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
discord.signal_role_create().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneRoles::OnRoleCreate));
|
||||
discord.signal_role_delete().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneRoles::OnRoleDelete));
|
||||
|
||||
const auto guild = *discord.GetGuild(GuildID);
|
||||
const auto roles = guild.FetchRoles();
|
||||
const bool can_modify = discord.HasGuildPermission(discord.GetUserData().ID, GuildID, Permission::MANAGE_ROLES);
|
||||
for (const auto &role : roles) {
|
||||
auto *row = Gtk::manage(new GuildSettingsRolesPaneRolesListItem(guild, role));
|
||||
row->drag_source_set(g_target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
|
||||
row->set_margin_start(5);
|
||||
row->set_halign(Gtk::ALIGN_FILL);
|
||||
row->show();
|
||||
m_rows[role.ID] = row;
|
||||
m_list.add(*row);
|
||||
if (can_modify)
|
||||
m_list.add_draggable(row);
|
||||
else
|
||||
m_list.add(*row);
|
||||
}
|
||||
|
||||
m_list.set_sort_func([this](Gtk::ListBoxRow *rowa_, Gtk::ListBoxRow *rowb_) -> int {
|
||||
@@ -108,12 +126,16 @@ GuildSettingsRolesPaneRoles::GuildSettingsRolesPaneRoles(Snowflake guild_id)
|
||||
void GuildSettingsRolesPaneRoles::OnRoleCreate(Snowflake guild_id, Snowflake role_id) {
|
||||
if (guild_id != GuildID) return;
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const bool can_modify = discord.HasGuildPermission(discord.GetUserData().ID, guild_id, Permission::MANAGE_ROLES);
|
||||
const auto guild = *discord.GetGuild(guild_id);
|
||||
const auto role = *discord.GetRole(role_id);
|
||||
auto *row = Gtk::manage(new GuildSettingsRolesPaneRolesListItem(guild, role));
|
||||
row->show();
|
||||
m_rows[role_id] = row;
|
||||
m_list.add(*row);
|
||||
if (can_modify)
|
||||
m_list.add_draggable(row);
|
||||
else
|
||||
m_list.add(*row);
|
||||
}
|
||||
|
||||
void GuildSettingsRolesPaneRoles::OnRoleDelete(Snowflake guild_id, Snowflake role_id) {
|
||||
@@ -205,6 +227,7 @@ GuildSettingsRolesPaneInfo::GuildSettingsRolesPaneInfo(Snowflake guild_id)
|
||||
if (!success) {
|
||||
m_color_button.set_rgba(IntToRGBA(discord.GetRole(RoleID)->Color));
|
||||
Gtk::MessageDialog dlg("Failed to set role color", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
@@ -364,6 +387,7 @@ void GuildSettingsRolesPaneInfo::UpdateRoleName() {
|
||||
if (!success) {
|
||||
m_role_name.set_text(discord.GetRole(RoleID)->Name);
|
||||
Gtk::MessageDialog dlg("Failed to set role name", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include "../../discord/guild.hpp"
|
||||
#include "../../components/draglistbox.hpp"
|
||||
|
||||
class GuildSettingsRolesPaneRolesListItem : public Gtk::ListBoxRow {
|
||||
public:
|
||||
@@ -32,7 +33,7 @@ private:
|
||||
|
||||
Gtk::Entry m_search;
|
||||
Gtk::ScrolledWindow m_list_scroll;
|
||||
Gtk::ListBox m_list;
|
||||
DragListBox m_list;
|
||||
|
||||
typedef sigc::signal<void, Snowflake /* role_id */> type_signal_role_select;
|
||||
type_signal_role_select m_signal_role_select;
|
||||
|
||||
Reference in New Issue
Block a user