mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2024-11-27 20:14:17 +01:00
Port yuzu-emu/yuzu#2529: "yuzu/bootmanager: Minor interface tid… (#4794)
* yuzu/bootmanager: Remove unnecessary pointer casts We can just invoke these functions by qualifying the object name before the function. * yuzu/bootmanager: unsigned -> u32 Same thing (for platforms we support), less reading. * yuzu/bootmanager: Default EmuThread's destructor in the cpp file This class contains non-trivial members, so we should default the destructor's definition within the cpp file. * yuzu/bootmanager: Treat the resolution factor as a u32 Treating it as a u16 can result in a sign-conversion warning when performing arithmetic with it, as u16 promotes to an int when aritmetic is performed on it, not unsigned int. This also makes the interface more uniform, as the layout interface now operates on u32 across the board. * yuzu/bootmanager: Log out screenshot destination path We can make this message more meaningful by indicating the location the screenshot has been saved to. We can also log out whenever a screenshot could not be saved (e.g. due to filesystem permissions or some other reason). * Fix compilation
This commit is contained in:
parent
2f7a10eeaa
commit
a546efad31
@ -252,8 +252,6 @@ void EmuWindow_SDL2::DoneCurrent() {
|
||||
SDL_GL_MakeCurrent(render_window, nullptr);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||
|
||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
||||
}
|
||||
|
@ -59,8 +59,7 @@ private:
|
||||
void Fullscreen();
|
||||
|
||||
/// Called when a configuration change affects the minimal size of the window
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||
|
||||
/// Is the window still open?
|
||||
bool is_open = true;
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
|
||||
|
||||
EmuThread::~EmuThread() = default;
|
||||
|
||||
void EmuThread::run() {
|
||||
render_window->MakeCurrent();
|
||||
|
||||
@ -160,14 +162,14 @@ void GRenderWindow::PollEvents() {}
|
||||
void GRenderWindow::OnFramebufferSizeChanged() {
|
||||
// Screen changes potentially incur a change in screen DPI, hence we should update the
|
||||
// framebuffer size
|
||||
qreal pixelRatio = windowPixelRatio();
|
||||
unsigned width = child->QPaintDevice::width() * pixelRatio;
|
||||
unsigned height = child->QPaintDevice::height() * pixelRatio;
|
||||
const qreal pixel_ratio = windowPixelRatio();
|
||||
const u32 width = child->QPaintDevice::width() * pixel_ratio;
|
||||
const u32 height = child->QPaintDevice::height() * pixel_ratio;
|
||||
UpdateCurrentFramebufferLayout(width, height);
|
||||
}
|
||||
|
||||
void GRenderWindow::BackupGeometry() {
|
||||
geometry = ((QGLWidget*)this)->saveGeometry();
|
||||
geometry = QWidget::saveGeometry();
|
||||
}
|
||||
|
||||
void GRenderWindow::RestoreGeometry() {
|
||||
@ -184,10 +186,11 @@ void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
|
||||
QByteArray GRenderWindow::saveGeometry() {
|
||||
// If we are a top-level widget, store the current geometry
|
||||
// otherwise, store the last backup
|
||||
if (parent() == nullptr)
|
||||
return ((QGLWidget*)this)->saveGeometry();
|
||||
else
|
||||
return geometry;
|
||||
if (parent() == nullptr) {
|
||||
return QWidget::saveGeometry();
|
||||
}
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
qreal GRenderWindow::windowPixelRatio() const {
|
||||
@ -195,10 +198,10 @@ qreal GRenderWindow::windowPixelRatio() const {
|
||||
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
|
||||
}
|
||||
|
||||
std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
|
||||
std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
|
||||
const qreal pixel_ratio = windowPixelRatio();
|
||||
return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
|
||||
static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
|
||||
return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
|
||||
static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
|
||||
}
|
||||
|
||||
void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||
@ -295,7 +298,7 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
||||
InputCommon::GetKeyboard()->ReleaseAllKeys();
|
||||
}
|
||||
|
||||
void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
|
||||
void GRenderWindow::OnClientAreaResized(u32 width, u32 height) {
|
||||
NotifyClientAreaSizeChanged(std::make_pair(width, height));
|
||||
}
|
||||
|
||||
@ -334,21 +337,25 @@ void GRenderWindow::InitRenderTarget() {
|
||||
BackupGeometry();
|
||||
}
|
||||
|
||||
void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
|
||||
if (!res_scale)
|
||||
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
||||
if (res_scale == 0)
|
||||
res_scale = VideoCore::GetResolutionScaleFactor();
|
||||
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
|
||||
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
||||
VideoCore::RequestScreenshot(screenshot_image.bits(),
|
||||
[=] {
|
||||
screenshot_image.mirrored(false, true).save(screenshot_path);
|
||||
LOG_INFO(Frontend, "The screenshot is saved.");
|
||||
},
|
||||
layout);
|
||||
VideoCore::RequestScreenshot(
|
||||
screenshot_image.bits(),
|
||||
[=] {
|
||||
const std::string std_screenshot_path = screenshot_path.toStdString();
|
||||
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
|
||||
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
|
||||
} else {
|
||||
LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
|
||||
}
|
||||
},
|
||||
layout);
|
||||
}
|
||||
|
||||
void GRenderWindow::OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||
void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||
setMinimumSize(minimal_size.first, minimal_size.second);
|
||||
}
|
||||
|
||||
|
@ -22,11 +22,12 @@ class GGLWidgetInternal;
|
||||
class GMainWindow;
|
||||
class GRenderWindow;
|
||||
|
||||
class EmuThread : public QThread {
|
||||
class EmuThread final : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EmuThread(GRenderWindow* render_window);
|
||||
~EmuThread() override;
|
||||
|
||||
/**
|
||||
* Start emulation (on new thread)
|
||||
@ -136,11 +137,11 @@ public:
|
||||
|
||||
void focusOutEvent(QFocusEvent* event) override;
|
||||
|
||||
void OnClientAreaResized(unsigned width, unsigned height);
|
||||
void OnClientAreaResized(u32 width, u32 height);
|
||||
|
||||
void InitRenderTarget();
|
||||
|
||||
void CaptureScreenshot(u16 res_scale, const QString& screenshot_path);
|
||||
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
|
||||
|
||||
public slots:
|
||||
void moveContext(); // overridden
|
||||
@ -154,13 +155,12 @@ signals:
|
||||
void Closed();
|
||||
|
||||
private:
|
||||
std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
|
||||
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
|
||||
void TouchBeginEvent(const QTouchEvent* event);
|
||||
void TouchUpdateEvent(const QTouchEvent* event);
|
||||
void TouchEndEvent();
|
||||
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||
|
||||
GGLWidgetInternal* child;
|
||||
|
||||
|
@ -146,8 +146,7 @@ private:
|
||||
* For the request to be honored, EmuWindow implementations will usually reimplement this
|
||||
* function.
|
||||
*/
|
||||
virtual void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) {
|
||||
virtual void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||
// By default, ignore this request and do nothing.
|
||||
}
|
||||
|
||||
|
@ -16,8 +16,8 @@ static const float TOP_SCREEN_ASPECT_RATIO =
|
||||
static const float BOT_SCREEN_ASPECT_RATIO =
|
||||
static_cast<float>(Core::kScreenBottomHeight) / Core::kScreenBottomWidth;
|
||||
|
||||
u16 FramebufferLayout::GetScalingRatio() const {
|
||||
return static_cast<u16>(((top_screen.GetWidth() - 1) / Core::kScreenTopWidth) + 1);
|
||||
u32 FramebufferLayout::GetScalingRatio() const {
|
||||
return static_cast<u32>(((top_screen.GetWidth() - 1) / Core::kScreenTopWidth) + 1);
|
||||
}
|
||||
|
||||
// Finds the largest size subrectangle contained in window area that is confined to the aspect ratio
|
||||
@ -30,17 +30,15 @@ static Common::Rectangle<T> maxRectangle(Common::Rectangle<T> window_area,
|
||||
static_cast<T>(std::round(scale * screen_aspect_ratio))};
|
||||
}
|
||||
|
||||
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height, bool swapped) {
|
||||
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped) {
|
||||
ASSERT(width > 0);
|
||||
ASSERT(height > 0);
|
||||
|
||||
FramebufferLayout res{width, height, true, true, {}, {}};
|
||||
// Default layout gives equal screen sizes to the top and bottom screen
|
||||
Common::Rectangle<unsigned> screen_window_area{0, 0, width, height / 2};
|
||||
Common::Rectangle<unsigned> top_screen =
|
||||
maxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<unsigned> bot_screen =
|
||||
maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2};
|
||||
Common::Rectangle<u32> top_screen = maxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<u32> bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
|
||||
|
||||
float window_aspect_ratio = static_cast<float>(height) / width;
|
||||
// both screens height are taken into account by multiplying by 2
|
||||
@ -70,18 +68,16 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height, bool swapp
|
||||
return res;
|
||||
}
|
||||
|
||||
FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool swapped) {
|
||||
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped) {
|
||||
ASSERT(width > 0);
|
||||
ASSERT(height > 0);
|
||||
// The drawing code needs at least somewhat valid values for both screens
|
||||
// so just calculate them both even if the other isn't showing.
|
||||
FramebufferLayout res{width, height, !swapped, swapped, {}, {}};
|
||||
|
||||
Common::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<unsigned> top_screen =
|
||||
maxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<unsigned> bot_screen =
|
||||
maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<u32> top_screen = maxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<u32> bot_screen = maxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
|
||||
|
||||
float window_aspect_ratio = static_cast<float>(height) / width;
|
||||
float emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
|
||||
@ -100,7 +96,7 @@ FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool swappe
|
||||
return res;
|
||||
}
|
||||
|
||||
FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped) {
|
||||
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped) {
|
||||
ASSERT(width > 0);
|
||||
ASSERT(height > 0);
|
||||
|
||||
@ -116,13 +112,11 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped
|
||||
float large_screen_aspect_ratio = swapped ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
|
||||
float small_screen_aspect_ratio = swapped ? TOP_SCREEN_ASPECT_RATIO : BOT_SCREEN_ASPECT_RATIO;
|
||||
|
||||
Common::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<unsigned> total_rect =
|
||||
maxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
Common::Rectangle<unsigned> large_screen = maxRectangle(total_rect, large_screen_aspect_ratio);
|
||||
Common::Rectangle<unsigned> fourth_size_rect = total_rect.Scale(.25f);
|
||||
Common::Rectangle<unsigned> small_screen =
|
||||
maxRectangle(fourth_size_rect, small_screen_aspect_ratio);
|
||||
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<u32> total_rect = maxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
Common::Rectangle<u32> large_screen = maxRectangle(total_rect, large_screen_aspect_ratio);
|
||||
Common::Rectangle<u32> fourth_size_rect = total_rect.Scale(.25f);
|
||||
Common::Rectangle<u32> small_screen = maxRectangle(fourth_size_rect, small_screen_aspect_ratio);
|
||||
|
||||
if (window_aspect_ratio < emulation_aspect_ratio) {
|
||||
large_screen =
|
||||
@ -139,7 +133,7 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped
|
||||
return res;
|
||||
}
|
||||
|
||||
FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool swapped) {
|
||||
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped) {
|
||||
ASSERT(width > 0);
|
||||
ASSERT(height > 0);
|
||||
|
||||
@ -148,13 +142,12 @@ FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool swapped)
|
||||
const float emulation_aspect_ratio = static_cast<float>(Core::kScreenTopHeight) /
|
||||
(Core::kScreenTopWidth + Core::kScreenBottomWidth);
|
||||
float window_aspect_ratio = static_cast<float>(height) / width;
|
||||
Common::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
|
||||
// Find largest Rectangle that can fit in the window size with the given aspect ratio
|
||||
Common::Rectangle<unsigned> screen_rect =
|
||||
maxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
Common::Rectangle<u32> screen_rect = maxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
// Find sizes of top and bottom screen
|
||||
Common::Rectangle<unsigned> top_screen = maxRectangle(screen_rect, TOP_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<unsigned> bot_screen = maxRectangle(screen_rect, BOT_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<u32> top_screen = maxRectangle(screen_rect, TOP_SCREEN_ASPECT_RATIO);
|
||||
Common::Rectangle<u32> bot_screen = maxRectangle(screen_rect, BOT_SCREEN_ASPECT_RATIO);
|
||||
|
||||
if (window_aspect_ratio < emulation_aspect_ratio) {
|
||||
// Apply borders to the left and right sides of the window.
|
||||
@ -173,16 +166,16 @@ FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool swapped)
|
||||
return res;
|
||||
}
|
||||
|
||||
FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
|
||||
FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
|
||||
ASSERT(width > 0);
|
||||
ASSERT(height > 0);
|
||||
|
||||
FramebufferLayout res{width, height, true, true, {}, {}};
|
||||
|
||||
Common::Rectangle<unsigned> top_screen{
|
||||
Common::Rectangle<u32> top_screen{
|
||||
Settings::values.custom_top_left, Settings::values.custom_top_top,
|
||||
Settings::values.custom_top_right, Settings::values.custom_top_bottom};
|
||||
Common::Rectangle<unsigned> bot_screen{
|
||||
Common::Rectangle<u32> bot_screen{
|
||||
Settings::values.custom_bottom_left, Settings::values.custom_bottom_top,
|
||||
Settings::values.custom_bottom_right, Settings::values.custom_bottom_bottom};
|
||||
|
||||
@ -191,7 +184,7 @@ FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
|
||||
return res;
|
||||
}
|
||||
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) {
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
|
||||
FramebufferLayout layout;
|
||||
if (Settings::values.custom_layout == true) {
|
||||
layout = CustomFrameLayout(
|
||||
|
@ -10,18 +10,18 @@ namespace Layout {
|
||||
|
||||
/// Describes the layout of the window framebuffer (size and top/bottom screen positions)
|
||||
struct FramebufferLayout {
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
u32 width;
|
||||
u32 height;
|
||||
bool top_screen_enabled;
|
||||
bool bottom_screen_enabled;
|
||||
Common::Rectangle<unsigned> top_screen;
|
||||
Common::Rectangle<unsigned> bottom_screen;
|
||||
Common::Rectangle<u32> top_screen;
|
||||
Common::Rectangle<u32> bottom_screen;
|
||||
|
||||
/**
|
||||
* Returns the ration of pixel size of the top screen, compared to the native size of the 3DS
|
||||
* screen.
|
||||
*/
|
||||
u16 GetScalingRatio() const;
|
||||
u32 GetScalingRatio() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -31,7 +31,7 @@ struct FramebufferLayout {
|
||||
* @param is_swapped if true, the bottom screen will be displayed above the top screen
|
||||
* @return Newly created FramebufferLayout object with default screen regions initialized
|
||||
*/
|
||||
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height, bool is_swapped);
|
||||
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped);
|
||||
|
||||
/**
|
||||
* Factory method for constructing a FramebufferLayout with only the top or bottom screen
|
||||
@ -40,7 +40,7 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height, bool is_sw
|
||||
* @param is_swapped if true, the bottom screen will be displayed (and the top won't be displayed)
|
||||
* @return Newly created FramebufferLayout object with default screen regions initialized
|
||||
*/
|
||||
FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool is_swapped);
|
||||
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped);
|
||||
|
||||
/**
|
||||
* Factory method for constructing a Frame with the a 4x size Top screen with a 1x size bottom
|
||||
@ -51,7 +51,7 @@ FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool is_swa
|
||||
* @param is_swapped if true, the bottom screen will be the large display
|
||||
* @return Newly created FramebufferLayout object with default screen regions initialized
|
||||
*/
|
||||
FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swapped);
|
||||
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped);
|
||||
|
||||
/**
|
||||
* Factory method for constructing a Frame with the Top screen and bottom
|
||||
@ -62,7 +62,7 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swap
|
||||
* @param is_swapped if true, the bottom screen will be the left display
|
||||
* @return Newly created FramebufferLayout object with default screen regions initialized
|
||||
*/
|
||||
FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool is_swapped);
|
||||
FramebufferLayout SideFrameLayout(u32 width, u32 height, bool is_swapped);
|
||||
|
||||
/**
|
||||
* Factory method for constructing a custom FramebufferLayout
|
||||
@ -70,13 +70,13 @@ FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool is_swapp
|
||||
* @param height Window framebuffer height in pixels
|
||||
* @return Newly created FramebufferLayout object with default screen regions initialized
|
||||
*/
|
||||
FramebufferLayout CustomFrameLayout(unsigned width, unsigned height);
|
||||
FramebufferLayout CustomFrameLayout(u32 width, u32 height);
|
||||
|
||||
/**
|
||||
* Convenience method to get frame layout by resolution scale
|
||||
* Read from the current settings to determine which layout to use.
|
||||
* @param res_scale resolution scale factor
|
||||
*/
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale);
|
||||
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale);
|
||||
|
||||
} // namespace Layout
|
||||
|
Loading…
Reference in New Issue
Block a user