diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 06c848c18..40971edd6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,6 +64,7 @@ jobs: -DSKIA_DIR=skia else cmake . -G Ninja \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9 \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ -DLAF_BACKEND=${{ matrix.backend }} \ -DSKIA_DIR=skia diff --git a/base/fs.cpp b/base/fs.cpp index 29311bda7..7ae6ef69e 100644 --- a/base/fs.cpp +++ b/base/fs.cpp @@ -1,5 +1,5 @@ // LAF Base Library -// Copyright (c) 2021-2022 Igara Studio S.A. +// Copyright (c) 2021-2024 Igara Studio S.A. // Copyright (c) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -27,16 +27,18 @@ namespace base { +// On Windows we can use \ or / as path separators, but on Unix-like +// platforms it's just /, as \ can be part of the file name. #if LAF_WINDOWS - const std::string::value_type path_separator = '\\'; + const std::string::value_type* path_separators = "\\/"; #else - const std::string::value_type path_separator = '/'; + const std::string::value_type* path_separators = "/"; #endif void make_all_directories(const std::string& path) { std::vector parts; - split_string(path, parts, "/\\"); + split_string(path, parts, path_separators); std::string intermediate; for (const std::string& component : parts) { @@ -55,31 +57,6 @@ void make_all_directories(const std::string& path) } } -std::string get_absolute_path(const std::string& filename) -{ - std::string fn = filename; - if (fn.size() > 2 && -#if LAF_WINDOWS - fn[1] != ':' -#else - fn[0] != '/' -#endif - ) { - fn = base::join_path(base::get_current_path(), fn); - } - fn = base::get_canonical_path(fn); - return fn; -} - -bool is_path_separator(std::string::value_type chr) -{ - return ( -#if LAF_WINDOWS - chr == '\\' || -#endif - chr == '/'); -} - std::string get_file_path(const std::string& filename) { std::string::const_reverse_iterator rit; @@ -213,10 +190,10 @@ std::string get_file_title_with_path(const std::string& filename) std::string get_relative_path(const std::string& filename, const std::string& base_path) { std::vector baseDirs; - split_string(base_path, baseDirs, "/\\"); + split_string(base_path, baseDirs, path_separators); std::vector toParts; - split_string(filename, toParts, "/\\"); + split_string(filename, toParts, path_separators); // Find the common prefix auto itFrom = baseDirs.begin(); @@ -270,19 +247,79 @@ std::string remove_path_separator(const std::string& path) std::string fix_path_separators(const std::string& filename) { - std::string result(filename); - - // Replace any separator with the system path separator. - std::replace_if(result.begin(), result.end(), - is_path_separator, path_separator); - + std::string result; + result.reserve(filename.size()); + for (auto chr : filename) { + if (is_path_separator(chr)) { + if (result.empty() || !is_path_separator(result.back())) + result.push_back(path_separator); + } + else + result.push_back(chr); + } return result; } -std::string normalize_path(const std::string& filename) +// It tries to replicate the standard path::lexically_normal() +// algorithm from https://en.cppreference.com/w/cpp/filesystem/path +std::string normalize_path(const std::string& _path) { - std::string fn = base::get_canonical_path(filename); - fn = base::fix_path_separators(fn); + // Normal form of an empty path is an empty path. + if (_path.empty()) + return std::string(); + + // Replace multiple slashes with a single path_separator. + std::string path = fix_path_separators(_path); + + std::string fn; + if (!path.empty() && path[0] == path_separator) + fn.push_back(path_separator); + + std::vector parts; + split_string(path, parts, path_separators); + + // Last element generates a final dot or slash in normalized path. + bool last_dot = false; + + auto n = int(parts.size()); + for (int i=0; i + +#if !LAF_MACOS + #define COMPARE_WITH_STD_FS 1 +#endif + +#if COMPARE_WITH_STD_FS + // We cannot use the on macOS yet because we are + // targetting macOS 10.9 platform. + #include + namespace fs = std::filesystem; +#endif + using namespace base; +#if COMPARE_WITH_STD_FS +// We want to test against std::filesystem for future replacement of +// some of our functions with the standard ones. +TEST(FS, CurrentPath) +{ + // Compare with + EXPECT_EQ(fs::current_path(), get_current_path()); + EXPECT_EQ(fs::path::preferred_separator, path_separator); +} +#endif + +TEST(FS, FixPathSeparators) +{ + const std::string sep(1, path_separator); + EXPECT_EQ(sep, fix_path_separators("/")); + EXPECT_EQ(sep, fix_path_separators("///")); + EXPECT_EQ("a"+sep+"b"+sep, fix_path_separators("a///b/")); +} + TEST(FS, MakeDirectory) { EXPECT_FALSE(is_directory("a")); @@ -191,10 +223,85 @@ TEST(FS, GetRelativePath) #endif } +TEST(FS, GetAbsolutePath) +{ + const auto cp = get_current_path(); + + EXPECT_EQ(join_path(cp, "a"), get_absolute_path("a")); + EXPECT_EQ(join_path(cp, "a"), get_absolute_path("./a")); + EXPECT_EQ(cp, get_absolute_path(".")); + EXPECT_EQ(cp, get_absolute_path("./.")); + EXPECT_EQ(cp, get_absolute_path("./a/..")); + EXPECT_EQ(cp, get_absolute_path(".////.")); + +#if LAF_WINDOWS + EXPECT_EQ("C:\\file", get_absolute_path("C:/path/../file")); +#else + EXPECT_EQ("/file", get_absolute_path("/path/../file")); +#endif +} + +TEST(FS, GetCanonicalPath) +{ + const auto cp = get_current_path(); + + EXPECT_EQ("", get_canonical_path("./non_existent_file")); + EXPECT_EQ("", get_canonical_path("non_existent_file")); + EXPECT_EQ(cp, get_canonical_path(".")); + + // Creates a file so get_canonical_path() returns its absolute path + write_file_content("_test_existing_file.txt", (uint8_t*)"123", 3); + EXPECT_EQ(join_path(cp, "_test_existing_file.txt"), + get_canonical_path("_test_existing_file.txt")); +} + +TEST(FS, NormalizePath) +{ + const std::string sep(1, path_separator); + + EXPECT_EQ("", normalize_path("")); + EXPECT_EQ(".", normalize_path(".")); + EXPECT_EQ(".", normalize_path("./.")); + EXPECT_EQ(".", normalize_path(".///./.")); + EXPECT_EQ(".", normalize_path(".///./")); + + EXPECT_EQ("a"+sep, normalize_path("a/.")); + EXPECT_EQ("a"+sep, normalize_path("a/")); + EXPECT_EQ("a", normalize_path("./a")); + EXPECT_EQ("a"+sep+"b"+sep+"c", normalize_path("a///b/./c")); + + EXPECT_EQ("..", normalize_path("..")); + EXPECT_EQ(".."+sep+"..", normalize_path("../..")); + EXPECT_EQ(".."+sep+"..", normalize_path("../../")); + EXPECT_EQ(".."+sep+"..", normalize_path(".././..")); + EXPECT_EQ(".."+sep+"..", normalize_path("./.././../.")); + + EXPECT_EQ(".", normalize_path("a/..")); + EXPECT_EQ("..", normalize_path("../a/..")); + EXPECT_EQ(".."+sep+"..", normalize_path("../a/../..")); + EXPECT_EQ("..", normalize_path("a/../..")); + EXPECT_EQ(sep+"b", normalize_path("/a/../b")); +} + +#if COMPARE_WITH_STD_FS +TEST(FS, CompareNormalizePathWithStd) +{ + for (const char* sample : { "", ".", "./.", ".///./.", ".///./", + "a/.", "a/", "./a", "a///b/./c", + "..", "../..", + "../../", ".././..", "./.././../.", + "a/..", "../a/..", "../a/../..", "a/../..", + "/a/../b" }) { + EXPECT_EQ(fs::path(sample).lexically_normal(), + normalize_path(sample)) + << " sample=\"" << sample << "\""; + } +} +#endif + TEST(FS, JoinPath) { - std::string sep; - sep.push_back(path_separator); + const std::string sep(1, path_separator); EXPECT_EQ("", join_path("", "")); EXPECT_EQ("fn", join_path("", "fn")); @@ -211,31 +318,31 @@ TEST(FS, JoinPath) TEST(FS, RemovePathSeparator) { - EXPECT_EQ("C:/foo", remove_path_separator("C:/foo/")); - EXPECT_EQ("C:\\foo\\main.cpp", remove_path_separator("C:\\foo\\main.cpp")); - EXPECT_EQ("C:\\foo\\main.cpp", remove_path_separator("C:\\foo\\main.cpp/")); + EXPECT_EQ("C:/foo", remove_path_separator("C:/foo/")); + EXPECT_EQ("C:\\foo\\main.cpp", remove_path_separator("C:\\foo\\main.cpp")); + EXPECT_EQ("C:\\foo\\main.cpp", remove_path_separator("C:\\foo\\main.cpp/")); #if LAF_WINDOWS - EXPECT_EQ("C:\\foo", remove_path_separator("C:\\foo\\")); + EXPECT_EQ("C:\\foo", remove_path_separator("C:\\foo\\")); #else - EXPECT_EQ("C:\\foo\\", remove_path_separator("C:\\foo\\")); + EXPECT_EQ("C:\\foo\\", remove_path_separator("C:\\foo\\")); #endif } TEST(FS, HasFileExtension) { - EXPECT_TRUE (has_file_extension("hi.png", base::paths{"png"})); - EXPECT_FALSE(has_file_extension("hi.png", base::paths{"pngg"})); - EXPECT_FALSE(has_file_extension("hi.png", base::paths{"ppng"})); - EXPECT_TRUE (has_file_extension("hi.jpeg", base::paths{"jpg","jpeg"})); - EXPECT_TRUE (has_file_extension("hi.jpg", base::paths{"jpg","jpeg"})); - EXPECT_FALSE(has_file_extension("hi.ase", base::paths{"jpg","jpeg"})); - EXPECT_TRUE (has_file_extension("hi.ase", base::paths{"jpg","jpeg","ase"})); - EXPECT_TRUE (has_file_extension("hi.ase", base::paths{"ase","jpg","jpeg"})); - - EXPECT_TRUE (has_file_extension("hi.png", base::paths{"Png"})); - EXPECT_TRUE (has_file_extension("hi.pnG", base::paths{"bmp","PNg"})); - EXPECT_TRUE (has_file_extension("hi.bmP", base::paths{"bMP","PNg"})); + EXPECT_TRUE (has_file_extension("hi.png", paths{"png"})); + EXPECT_FALSE(has_file_extension("hi.png", paths{"pngg"})); + EXPECT_FALSE(has_file_extension("hi.png", paths{"ppng"})); + EXPECT_TRUE (has_file_extension("hi.jpeg", paths{"jpg","jpeg"})); + EXPECT_TRUE (has_file_extension("hi.jpg", paths{"jpg","jpeg"})); + EXPECT_FALSE(has_file_extension("hi.ase", paths{"jpg","jpeg"})); + EXPECT_TRUE (has_file_extension("hi.ase", paths{"jpg","jpeg","ase"})); + EXPECT_TRUE (has_file_extension("hi.ase", paths{"ase","jpg","jpeg"})); + + EXPECT_TRUE (has_file_extension("hi.png", paths{"Png"})); + EXPECT_TRUE (has_file_extension("hi.pnG", paths{"bmp","PNg"})); + EXPECT_TRUE (has_file_extension("hi.bmP", paths{"bMP","PNg"})); } TEST(FS, ReplaceExtension) @@ -284,13 +391,13 @@ TEST(FS, CopyFiles) std::vector data = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' }; const std::string dst = "_test_copy_.tmp"; - if (base::is_file(dst)) - base::delete_file(dst); + if (is_file(dst)) + delete_file(dst); - base::write_file_content("_test_orig_.tmp", data.data(), data.size()); - base::copy_file("_test_orig_.tmp", dst, true); + write_file_content("_test_orig_.tmp", data.data(), data.size()); + copy_file("_test_orig_.tmp", dst, true); - EXPECT_EQ(data, base::read_file_content(dst)); + EXPECT_EQ(data, read_file_content(dst)); } int main(int argc, char** argv) diff --git a/base/fs_unix.h b/base/fs_unix.h index e4b0c830e..3d8c2e846 100644 --- a/base/fs_unix.h +++ b/base/fs_unix.h @@ -6,6 +6,7 @@ // Read LICENSE.txt for more information. #include "base/file_handle.h" +#include "base/fs.h" #include "base/ints.h" #include "base/paths.h" #include "base/time.h" @@ -208,11 +209,24 @@ std::string get_user_docs_folder() std::string get_canonical_path(const std::string& path) { + const std::string full = get_absolute_path(path); char buffer[PATH_MAX]; // Ignore return value as realpath() returns nullptr anyway when the // resolved_path parameter is specified. - realpath(path.c_str(), buffer); - return buffer; + if (realpath(full.c_str(), buffer)) + return buffer; // No error, the file/dir exists + return std::string(); +} + +std::string get_absolute_path(const std::string& path) +{ + std::string full = path; + if (!full.empty() && full[0] != '/') + full = join_path(get_current_path(), full); + full = normalize_path(full); + if (!full.empty() && full.back() == path_separator) + full.erase(full.size()-1); + return full; } paths list_files(const std::string& path) diff --git a/base/fs_win32.h b/base/fs_win32.h index 3edc6d12d..6366ff1a6 100644 --- a/base/fs_win32.h +++ b/base/fs_win32.h @@ -5,17 +5,18 @@ // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. -#include -#include -#include -#include - +#include "base/fs.h" #include "base/paths.h" #include "base/string.h" #include "base/time.h" #include "base/version.h" #include "base/win/win32_exception.h" +#include +#include +#include +#include + namespace base { bool is_file(const std::string& path) @@ -119,8 +120,7 @@ std::string get_current_path() TCHAR buffer[MAX_PATH+1]; if (::GetCurrentDirectory(sizeof(buffer)/sizeof(TCHAR), buffer)) return to_utf8(buffer); - else - return ""; + return std::string(); } void set_current_path(const std::string& path) @@ -133,8 +133,7 @@ std::string get_app_path() TCHAR buffer[MAX_PATH+1]; if (::GetModuleFileName(NULL, buffer, sizeof(buffer)/sizeof(TCHAR))) return to_utf8(buffer); - else - return ""; + return std::string(); } std::string get_temp_path() @@ -152,15 +151,29 @@ std::string get_user_docs_folder() buffer); if (hr == S_OK) return to_utf8(buffer); - else - return ""; + return std::string(); } std::string get_canonical_path(const std::string& path) { + std::string full = get_absolute_path(path); + DWORD attr = ::GetFileAttributes(from_utf8(full).c_str()); + if (attr != INVALID_FILE_ATTRIBUTES) + return full; + return std::string(); +} + +std::string get_absolute_path(const std::string& path) +{ + std::string full; + if (path.size() > 2 && path[1] != ':') + full = base::join_path(base::get_current_path(), path); + else + full = path; + TCHAR buffer[MAX_PATH+1]; GetFullPathName( - from_utf8(path).c_str(), + from_utf8(full).c_str(), sizeof(buffer)/sizeof(TCHAR), buffer, nullptr); diff --git a/base/mem_utils.cpp b/base/mem_utils.cpp index 3d52957a4..692b3b5ba 100644 --- a/base/mem_utils.cpp +++ b/base/mem_utils.cpp @@ -21,7 +21,7 @@ string get_pretty_memory_size(size_t memsize) char buf[256]; if (memsize < 1000) { - std::snprintf(buf, sizeof(buf), "%lu bytes", memsize); + std::snprintf(buf, sizeof(buf), "%zu bytes", memsize); } else if (memsize < 1000*1000) { std::snprintf(buf, sizeof(buf), "%0.1fK", memsize/1024.0f); diff --git a/gfx/clip.h b/gfx/clip.h index b5899b51d..a33866234 100644 --- a/gfx/clip.h +++ b/gfx/clip.h @@ -15,6 +15,55 @@ namespace gfx { + // Imagine you want to copy the following rectangle + // + // size.w + // +---------+ + // | | size.h + // +---------+ + // + // from a "source image" to a "destination image", from "src" + // location to the "dst" location: + // + // "source image" "destination image" + // +--------------+ +---------------------+ + // src.xy | | | dst.xy | + // +---------+ | | +---------+ + // | | | ---> | | | + // +---------+ | | +---------+ + // | | | | + // +--------------+ +---------------------+ + // + // There are two restrictions: + // + // 1) You cannot read from the "source image" outside its bounds, and + // 2) you cannot write into the "destination image" outside its bounds. + // + // This Clip class helps to determine the only valid region to + // read/write between these two images. The initial Clip values can + // be outside these bounds, but the Clip::clip() function determines + // the valid region. + // + // So in our example, if the "source image" has a dimension of + // "avail_src_w/h" and the "destination image" a dimension of + // "avail_dst_w/h", after calling Clip::clip() we get these modified + // Clip fields: + // + // "source image" "destination image" + // +--------------+ +---------------------+ + // src.xy | | dst.xy | + // .....+---+. | | .....+---+. + // .....| |. | ---> | .....| |. + // .....+---+. | | .....+---+. + // | | | | + // +--------------+ +---------------------+ + // + // The dotted areas (...) are invalid image regions that cannot be + // read/written. The "dst", "src", and "size" fields of the Clip + // instance are adjusted to be inside both images. + // + // Clip::clip() returns false in case there is no valid area to + // copy. template class ClipT { public: diff --git a/gfx/rect.h b/gfx/rect.h index 0e3a2ca3a..9acd18a2c 100644 --- a/gfx/rect.h +++ b/gfx/rect.h @@ -409,6 +409,81 @@ class RectT { return *this; } + // Slices vertically this Rect along the provided px coordinate. + // Sets the left and right rects in the references of the same name. + const RectT& sliceV(T px, RectT& left, RectT& right) const { + if (px < x) { + left = RectT(); + right = *this; + } + else if (px > x2()) { + left = *this; + right = RectT(); + } + else { + left = RectT(x, y, px - x, h); + right = RectT(px, y, x2() - px, h); + } + + return *this; + } + + // Slices horizontally this Rect along the provided py coordinate. + // Sets the top and bottom rects in the references of the same name. + const RectT& sliceH(T py, RectT& top, RectT& bottom) const { + if (py < y) { + top = RectT(); + bottom = *this; + } + else if (py > y2()) { + top = *this; + bottom = RectT(); + } + else { + top = RectT(x, y, w, py - y); + bottom = RectT(x, py, w, y2() - py); + } + + return *this; + } + + // Slices this rect in nine pieces and returns all the rects in the slices + // output array. The center rect defines the relative coordinates where the + // cuts are going to be made: + // + // this (x, y, w=23, h=7) slices output + // +---------------------+ +--------+-----+------+ + // | center (9,2,w=7,h=3)| | [0] | [1] | [2] | + // | +-----+ | +--------+-----+------+ + // | | | | => | [3] | [4] | [5] | + // | +-----+ | +--------+-----+------+ + // | | | [6] | [7] | [8] | + // +---------------------+ +--------+-----+------+ + // + const RectT& nineSlice(const RectT& center, RectT slices[9]) const { + gfx::RectT left, middle, right; + + { + gfx::RectT remaining; + this->sliceV(x + center.x, left, remaining); + remaining.sliceV(x + center.x2(), middle, right); + } + + left .sliceH(y + center.y , slices[0], left); + middle.sliceH(y + center.y , slices[1], middle); + right .sliceH(y + center.y , slices[2], right); + + left .sliceH(y + center.y2(), slices[3], left); + middle.sliceH(y + center.y2(), slices[4], middle); + right .sliceH(y + center.y2(), slices[5], right); + + slices[6] = left; + slices[7] = middle; + slices[8] = right; + + return *this; + } + }; using Rect = RectT; diff --git a/gfx/rect_tests.cpp b/gfx/rect_tests.cpp index 937921e39..23519a803 100644 --- a/gfx/rect_tests.cpp +++ b/gfx/rect_tests.cpp @@ -64,6 +64,113 @@ TEST(Rect, Floor) EXPECT_EQ(gfx::Rect(-1, -1, 1, 2), gfx::RectF(-0.25, -0.75, 1, 2).floor()); } + +TEST(Rect, SliceV) +{ + const int x = 3, y = 4; + gfx::Rect l, r; + auto rect = gfx::Rect(x, y, 5, 7); + rect.sliceV(x, l, r); + EXPECT_EQ(gfx::Rect(x,y,0,7), l); + EXPECT_EQ(gfx::Rect(x,y,5,7), r); + + rect.sliceV(x-1, l, r); + EXPECT_EQ(gfx::Rect(0,0,0,0), l); + EXPECT_EQ(gfx::Rect(x,y,5,7), r); + + rect.sliceV(x+1, l, r); + EXPECT_EQ(gfx::Rect(x,y,1,7), l); + EXPECT_EQ(gfx::Rect(x+1,y,4,7), r); + + rect.sliceV(x+4, l, r); + EXPECT_EQ(gfx::Rect(x,y,4,7), l); + EXPECT_EQ(gfx::Rect(x+4,y,1,7), r); + + rect.sliceV(x+5, l, r); + EXPECT_EQ(gfx::Rect(x,y,5,7), l); + EXPECT_EQ(gfx::Rect(x+5,y,0,7), r); + + rect.sliceV(x+6, l, r); + EXPECT_EQ(gfx::Rect(x,y,5,7), l); + EXPECT_EQ(gfx::Rect(0,0,0,0), r); +} + +TEST(Rect, SliceH) +{ + const int x = 3, y = 4; + gfx::Rect t, b; + auto rect = gfx::Rect(x, y, 5, 7); + rect.sliceH(y, t, b); + EXPECT_EQ(gfx::Rect(x,y,5,0), t); + EXPECT_EQ(gfx::Rect(x,y,5,7), b); + + rect.sliceH(y-1, t, b); + EXPECT_EQ(gfx::Rect(0,0,0,0), t); + EXPECT_EQ(gfx::Rect(x,y,5,7), b); + + rect.sliceH(y+1, t, b); + EXPECT_EQ(gfx::Rect(x,y,5,1), t); + EXPECT_EQ(gfx::Rect(x,y+1,5,6), b); + + rect.sliceH(y+6, t, b); + EXPECT_EQ(gfx::Rect(x,y,5,6), t); + EXPECT_EQ(gfx::Rect(x,y+6,5,1), b); + + rect.sliceH(y+7, t, b); + EXPECT_EQ(gfx::Rect(x,y,5,7), t); + EXPECT_EQ(gfx::Rect(x,y+7,5,0), b); + + rect.sliceH(y+8, t, b); + EXPECT_EQ(gfx::Rect(x,y,5,7), t); + EXPECT_EQ(gfx::Rect(0,0,0,0), b); +} + +TEST(Rect, NineSlice) +{ + const int x = 3, y = 4; + auto rect = gfx::Rect(x, y, 6, 6); + gfx::Rect slices[9]; + + // Slice using an inner rect. + rect.nineSlice(gfx::Rect(3, 3, 2, 2), slices); + EXPECT_EQ(gfx::Rect(x,y,6,6), rect); + EXPECT_EQ(gfx::Rect(x ,y ,3,3), slices[0]); + EXPECT_EQ(gfx::Rect(x+3,y ,2,3), slices[1]); + EXPECT_EQ(gfx::Rect(x+5,y ,1,3), slices[2]); + EXPECT_EQ(gfx::Rect(x ,y+3,3,2), slices[3]); + EXPECT_EQ(gfx::Rect(x+3,y+3,2,2), slices[4]); + EXPECT_EQ(gfx::Rect(x+5,y+3,1,2), slices[5]); + EXPECT_EQ(gfx::Rect(x ,y+5,3,1), slices[6]); + EXPECT_EQ(gfx::Rect(x+3,y+5,2,1), slices[7]); + EXPECT_EQ(gfx::Rect(x+5,y+5,1,1), slices[8]); + + // Slice using a center rect with the same size as the rect being sliced. + rect.nineSlice(gfx::Rect(0, 0, 6, 6), slices); + EXPECT_EQ(gfx::Rect(x,y,6,6), rect); + EXPECT_EQ(gfx::Rect(x ,y ,0,0), slices[0]); + EXPECT_EQ(gfx::Rect(x ,y ,6,0), slices[1]); + EXPECT_EQ(gfx::Rect(x+6,y ,0,0), slices[2]); + EXPECT_EQ(gfx::Rect(x ,y ,0,6), slices[3]); + EXPECT_EQ(gfx::Rect(x ,y ,6,6), slices[4]); + EXPECT_EQ(gfx::Rect(x+6,y ,0,6), slices[5]); + EXPECT_EQ(gfx::Rect(x ,y+6,0,0), slices[6]); + EXPECT_EQ(gfx::Rect(x ,y+6,6,0), slices[7]); + EXPECT_EQ(gfx::Rect(x+6,y+6,0,0), slices[8]); + + // Slice using an outer rect. + rect.nineSlice(gfx::Rect(-1, -1, 8, 8), slices); + EXPECT_EQ(gfx::Rect(x,y,6,6), rect); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[0]); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[1]); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[2]); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[3]); + EXPECT_EQ(gfx::Rect(x,y,6,6), slices[4]); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[5]); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[6]); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[7]); + EXPECT_EQ(gfx::Rect(0,0,0,0), slices[8]); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/os/osx/event_queue.h b/os/osx/event_queue.h index a6b96736f..b0de33beb 100644 --- a/os/osx/event_queue.h +++ b/os/osx/event_queue.h @@ -9,11 +9,11 @@ #define OS_OSX_EVENT_QUEUE_INCLUDED #pragma once -#include "base/concurrent_queue.h" #include "os/event.h" #include "os/event_queue.h" -#include +#include +#include namespace os { @@ -28,8 +28,9 @@ class EventQueueOSX : public EventQueue { private: void wakeUpQueue(); - base::concurrent_queue m_events; - std::atomic m_sleeping; + std::deque m_events; + mutable std::mutex m_mutex; + bool m_sleeping; }; using EventQueueImpl = EventQueueOSX; diff --git a/os/osx/event_queue.mm b/os/osx/event_queue.mm index 419fb2949..8e93925bf 100644 --- a/os/osx/event_queue.mm +++ b/os/osx/event_queue.mm @@ -1,5 +1,5 @@ // LAF OS Library -// Copyright (C) 2018-2021 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2015-2017 David Capello // // This file is released under the terms of the MIT license. @@ -74,31 +74,47 @@ } } while (event); - if (!m_events.try_pop(ev)) { + { + // Note that we don't use the try_lock because we can wait for + // the lock, since there is no long running functions that might get the + // lock for a long time. + const std::lock_guard lock(m_mutex); + if (!m_events.empty()) { + ev = m_events.front(); + m_events.pop_front(); + return; + } + if (timeout == kWithoutTimeout) EV_TRACE("EV: Waiting for events\n"); - // Wait until there is a Cocoa event in queue m_sleeping = true; - event = [app nextEventMatchingMask:NSEventMaskAny - untilDate:untilDate - inMode:NSDefaultRunLoopMode - dequeue:YES]; + } + + // Wait until there is a Cocoa event in queue + event = [app nextEventMatchingMask:NSEventMaskAny + untilDate:untilDate + inMode:NSDefaultRunLoopMode + dequeue:YES]; + + { + const std::lock_guard lock(m_mutex); m_sleeping = false; + } - if (event) { - EV_TRACE("EV: Event received!\n"); - goto retry; - } - else { - EV_TRACE("EV: Timeout!"); - } + if (event) { + EV_TRACE("EV: Event received!\n"); + goto retry; + } + else { + EV_TRACE("EV: Timeout!"); } } } void EventQueueOSX::queueEvent(const Event& ev) { + const std::lock_guard lock(m_mutex); if (m_sleeping) { // Wake up the macOS event queue. This is necessary in case that we // change the display color profile from macOS settings: the @@ -111,7 +127,7 @@ wakeUpQueue(); m_sleeping = false; } - m_events.push(ev); + m_events.push_back(ev); } void EventQueueOSX::wakeUpQueue() @@ -136,6 +152,7 @@ void EventQueueOSX::clearEvents() { + const std::lock_guard lock(m_mutex); m_events.clear(); } diff --git a/os/x11/window.cpp b/os/x11/window.cpp index bf4772604..21876c58f 100644 --- a/os/x11/window.cpp +++ b/os/x11/window.cpp @@ -481,13 +481,20 @@ void WindowX11::setScale(const int scale) bool WindowX11::isVisible() const { - // TODO - return true; + XWindowAttributes attr; + memset(&attr, 0, sizeof(attr)); + Status status = XGetWindowAttributes(m_display, m_window, &attr); + return ((attr.map_state & IsViewable) == IsViewable); } void WindowX11::setVisible(bool visible) { - // TODO + if (visible) { + XMapWindow(m_display, m_window); + } + else { + XUnmapWindow(m_display, m_window); + } } void WindowX11::activate()