Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8a57605
300 Update sh_template so that MacOS can support wxWidgets
Johnnyg63 Sep 17, 2026
d1605f8
[github] update cmake command to use dev as source directory
Moros1138 Sep 17, 2026
435f7b8
Update README.md
tgd2 Sep 19, 2026
352c340
Merge pull request #301 from OneLoneCoder/300-update-sh_template-so-t…
OneLoneCoder Sep 19, 2026
a999bf8
Merge pull request #303 from OneLoneCoder/github-fix-single-header-check
OneLoneCoder Sep 19, 2026
991f9d7
Merge pull request #304 from OneLoneCoder/linux-linker-tweak
OneLoneCoder Sep 19, 2026
ba5c9e4
[cmake] revamp build system, remove code duplication
Moros1138 Sep 15, 2026
4304b29
[ext][miniaudio] scaffold build, assets
Moros1138 Sep 15, 2026
6db73a3
[ext][miniaudio] add extension, demo, and flesh out README.md
Moros1138 Sep 15, 2026
aeb2238
[cmake] remove root level cmake file, restructure appropriately
Moros1138 Sep 15, 2026
788b538
[docs] add extensions documentation to the cmake section
Moros1138 Sep 16, 2026
e51b11d
[ext][miniaudio][cmake] rename olcPGE3_Miniaudio to ext_demo_Miniaudio
Moros1138 Sep 20, 2026
c8ed6fc
[ext][miniaudio][demo] add demo
Moros1138 Sep 20, 2026
752704b
[ext][miniaudio][docs] final pass on documentation pre-release
Moros1138 Sep 20, 2026
2942cc0
Only copy the header if it is different
dandistine Sep 20, 2026
e8082f0
Change to copy_if_different since the github runner is out-of-date.
dandistine Sep 20, 2026
ad2610f
Allow the x11 event queue to block if no events are present, saving c…
dandistine Sep 20, 2026
5bcf847
Apply the polling change to wayland too.
dandistine Sep 20, 2026
d74c771
Allow unpacking of v_2d and v_4d via structured bindings.
dandistine Sep 21, 2026
ebdff87
Implement a test / demo for v_4d
dandistine Sep 21, 2026
b59739d
Sneak this one in before javid notices that it was missing.
dandistine Sep 21, 2026
9fd3445
Merge pull request #309 from OneLoneCoder/vector_unpacking
OneLoneCoder Sep 23, 2026
b4f3ad8
Merge pull request #310 from OneLoneCoder/vector_4d_minus_equals
OneLoneCoder Sep 23, 2026
2cbca77
Merge pull request #305 from OneLoneCoder/miniaudio-pgex
OneLoneCoder Sep 23, 2026
345dffa
Merge pull request #307 from OneLoneCoder/x11_event_loop_polling
OneLoneCoder Sep 23, 2026
fd72c17
Merge pull request #308 from OneLoneCoder/wayland_event_loop_polling
OneLoneCoder Sep 23, 2026
994c30a
Added SetPixel(), SetRawPixel() & SetUnsafeRawPixel()
OneLoneCoder Sep 23, 2026
cfd0945
Merge pull request #313 from OneLoneCoder/296-request-for-pixel-acces…
OneLoneCoder Sep 23, 2026
14e2927
311-various-macos-issues-mostly-to-do-with-mouse-inputs
Johnnyg63 Sep 24, 2026
8a51d72
Merge pull request #314 from OneLoneCoder/311-various-macos-issues-mo…
OneLoneCoder Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check-single-header.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

- name: Build and run single header amalgamation tool (olcGimmeHead)
run: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake -S dev -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config=Release

- name: Check that both headers are identical
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ Header file should now be located at `build/olcPixelGameEngine3.h`.

### Available CMake Configuration Flags

* ``-DBUILD_EXAMPLES=ON`` - builds examples, will be located at `build/examples`
* ``-DBUILD_EXAMPLES=ON`` - builds examples, will be located at `build/dev/examples`
* ``-DBUILD_EXTENSIONS=ON`` - builds examples, will be located at `build/dev/extensions`
* ``-DBUILD_WAYLAND=ON`` - if examples are built, build them using wayland. Linux ONLY!
* ``-DUSE_STB=ON`` - build using stb_image for the image loader (See section on STB Image)

Expand All @@ -138,13 +139,13 @@ cd examples
### Using GCC

```bash
g++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lpthread
g++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lXi -lpthread
```

### Using Clang

```bash
clang++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lpthread
clang++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lXi -lpthread
```

### Using Emscripten
Expand Down
3 changes: 2 additions & 1 deletion dev/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ else()
endif()

add_custom_target(CopyHeader
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/olcPixelGameEngine3.h ${REPO_ROOT_DIR}/olcPixelGameEngine3.h
BYPRODUCTS ${REPO_ROOT_DIR}/olcPixelGameEngine3.h
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/olcPixelGameEngine3.h ${REPO_ROOT_DIR}/olcPixelGameEngine3.h
DEPENDS olcPixelGameEngine3.h
)

Expand Down
2 changes: 2 additions & 0 deletions dev/msvc/olcPGE3.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\extensions\miniaudio\olcPGEX3_Miniaudio.h" />
<ClInclude Include="..\..\utilities\olcUTIL3_Animate2D.h" />
<ClInclude Include="..\..\utilities\olcUTIL3_Camera2D.h" />
<ClInclude Include="..\..\utilities\olcUTIL3_GameMode.h" />
Expand Down Expand Up @@ -276,6 +277,7 @@
<ClInclude Include="..\src\window.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\extensions\miniaudio\demo_Miniaudio.cpp" />
<ClCompile Include="..\src\api_macos.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
Expand Down
6 changes: 6 additions & 0 deletions dev/msvc/olcPGE3.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@
<ClInclude Include="..\src\host_wxwidgets.h">
<Filter>Hosts\wxWidgets Specific</Filter>
</ClInclude>
<ClInclude Include="..\..\extensions\miniaudio\olcPGEX3_Miniaudio.h">
<Filter>Extensions\MiniAudio</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\core.cpp">
Expand Down Expand Up @@ -278,6 +281,9 @@
<ClCompile Include="..\src\host_wxwidgets.cpp">
<Filter>Hosts\wxWidgets Specific</Filter>
</ClCompile>
<ClCompile Include="..\..\extensions\miniaudio\demo_Miniaudio.cpp">
<Filter>Extensions\MiniAudio</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md">
Expand Down
71 changes: 25 additions & 46 deletions dev/src/api_macos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ static constexpr const char* kSetContentViewSel = "setContentVie
static constexpr const char* kContentViewSel = "contentView";
static constexpr const char* kBoundsSel = "bounds";
static constexpr const char* kConvertRectToBackingSel = "convertRectToBacking:"; // Thank you - Ben the Ultimate Guru
static constexpr const char* kConvertRectFromBackingSel = "convertRectFromBacking:"; // Thank you - Mr B the Ultimate Teacher!!!
static constexpr const char* kConvertPointToBackingSel = "convertPointToBacking:";
static constexpr const char* kConvertPointFromBackingSel = "convertPointFromBacking:";
static constexpr const char* kConvertPointFromViewSel = "convertPoint:fromView:";
Expand Down Expand Up @@ -330,6 +331,7 @@ namespace ObjectiveCSEL {
static SEL contentViewSel = nullptr;
static SEL boundsSel = nullptr;
static SEL convertRectToBackingSel = nullptr; // Thank you - Ben the Ultimate Guru
static SEL convertRectFromBackingSel = nullptr; // Thank you - Mr B the Ultimate Teacher!!!
static SEL convertPointToBackingSel = nullptr;
static SEL convertPointFromBackingSel = nullptr;
static SEL convertPointFromViewSel = nullptr;
Expand Down Expand Up @@ -479,6 +481,7 @@ namespace ObjectiveCSEL {
contentViewSel = sel_registerName(kContentViewSel);
boundsSel = sel_registerName(kBoundsSel);
convertRectToBackingSel = sel_registerName(kConvertRectToBackingSel); // Thank you - Ben the Ultimate Guru
convertRectFromBackingSel = sel_registerName(kConvertRectFromBackingSel); // Thank you - Mr B the Ultimate Teacher!!!
convertPointToBackingSel = sel_registerName(kConvertPointToBackingSel);
convertPointFromBackingSel = sel_registerName(kConvertPointFromBackingSel);
convertPointFromViewSel = sel_registerName(kConvertPointFromViewSel);
Expand Down Expand Up @@ -1365,8 +1368,8 @@ void view_mouseDown(id self, SEL _cmd, id event) {
CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);

// Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)?
if (nPointerEventType == 0) [[likely]] {
// Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B
if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] {

if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDownCallback) [[likely]] {
MouseEventData data = extractMouseEventData(event);
Expand Down Expand Up @@ -1397,8 +1400,8 @@ void view_mouseUp(id self, SEL _cmd, id event) {
CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);

// Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)?
if (nPointerEventType == 0) [[likely]] {
// Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B
if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] {

MouseEventData data = extractMouseEventData(event);
if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseUpCallback) [[likely]] {
Expand Down Expand Up @@ -1429,8 +1432,8 @@ void view_mouseDragged(id self, SEL _cmd, id event) {
CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);

// Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)?
if (nPointerEventType == 0) [[likely]] {
// Is it a mouse drag event (subtype 0) or a tablet pointer event (subtype 1 or 2)? Thanks Mr B
if (nPointerEventType != kCGEventMouseSubtypeTabletPoint && nPointerEventType != kCGEventMouseSubtypeTabletProximity) [[likely]] {

if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseDraggedCallback) [[likely]] {
MouseEventData data = extractMouseEventData(event);
Expand All @@ -1456,12 +1459,7 @@ void view_mouseDragged(id self, SEL _cmd, id event) {
// Handle mouse movement events
void view_mouseMoved(id self, SEL _cmd, id event) {
(void)self;(void)_cmd;

CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);
// if it is a trackpad event, ignore it.
if (nPointerEventType > 2) return;


// a Mouse move event and Stlus roximity Event have the same properties.
NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel);
NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel);
Expand All @@ -1476,12 +1474,7 @@ void view_mouseMoved(id self, SEL _cmd, id event) {
// Handle right mouse down events
void view_rightMouseDown(id self, SEL _cmd, id event) {
(void)self;(void)_cmd;

CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);
if(nPointerEventType > 2) [[unlikely]]
return; // Ignore mouse events that are not mouse drag or tablet pointer events


MouseEventData data = extractMouseEventData(event);
if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDownCallback) [[likely]] {
gptrNSWindowEvents->rightMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData);
Expand All @@ -1492,11 +1485,6 @@ void view_rightMouseDown(id self, SEL _cmd, id event) {
void view_rightMouseUp(id self, SEL _cmd, id event) {
(void)self;(void)_cmd;

CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);
if(nPointerEventType > 2) [[unlikely]]
return; // Ignore mouse events that are not mouse drag or tablet pointer events

MouseEventData data = extractMouseEventData(event);
if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseUpCallback) [[likely]] {
gptrNSWindowEvents->rightMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData);
Expand All @@ -1506,11 +1494,6 @@ void view_rightMouseUp(id self, SEL _cmd, id event) {
// Handle right mouse drag events
void view_rightMouseDragged(id self, SEL _cmd, id event) {
(void)self;(void)_cmd;

CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);
if(nPointerEventType > 2) [[unlikely]]
return; // Ignore mouse events that are not mouse drag or tablet pointer events

MouseEventData data = extractMouseEventData(event);
if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->rightMouseDraggedCallback) [[likely]] {
Expand All @@ -1523,11 +1506,6 @@ void view_rightMouseDragged(id self, SEL _cmd, id event) {
void view_otherMouseDown(id self, SEL _cmd, id event) {
(void)self;(void)_cmd;

CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);
if(nPointerEventType > 2) [[unlikely]]
return; // Ignore mouse events that are not mouse drag or tablet pointer events

MouseEventData data = extractMouseEventData(event);
if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDownCallback) [[likely]] {
gptrNSWindowEvents->otherMouseDownCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData);
Expand All @@ -1538,11 +1516,6 @@ void view_otherMouseDown(id self, SEL _cmd, id event) {
void view_otherMouseUp(id self, SEL _cmd, id event) {
(void)self;(void)_cmd;

CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);
if(nPointerEventType > 2) [[unlikely]]
return; // Ignore mouse events that are not mouse drag or tablet pointer events

MouseEventData data = extractMouseEventData(event);
if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseUpCallback) {
gptrNSWindowEvents->otherMouseUpCallback(data.location.x, data.location.y, (int)data.buttonNumber, (unsigned int)data.modifierFlags, gptrNSWindowEvents->eventUserData);
Expand All @@ -1551,11 +1524,6 @@ void view_otherMouseUp(id self, SEL _cmd, id event) {

void view_otherMouseDragged(id self, SEL _cmd, id event) {
(void)self;(void)_cmd;

CGEventRef cgEvent = ((CGEventRef(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::cgEventSel);
uint8_t nPointerEventType = CGEventGetIntegerValueField(cgEvent, kCGMouseEventSubtype);
if(nPointerEventType > 2) [[unlikely]]
return; // Ignore mouse events that are not mouse drag or tablet pointer events

MouseEventData data = extractMouseEventData(event);
if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->otherMouseDraggedCallback) [[likely]] {
Expand Down Expand Up @@ -2100,7 +2068,17 @@ extern "C" {

// Destroy the window
void window_destroy(Window* self) {
(void)self;

// Thanks Mr B!
if (self) {
self->acceptsInputEvents = NO;
if (gptrNSWindowEvents == self) {
gptrNSWindowEvents = NULL;
}
if (gptrWindowDelegate == self) {
gptrWindowDelegate = NULL;
}
}
// NSWindow will be cleaned up by autorelease pool
}

Expand Down Expand Up @@ -2411,8 +2389,9 @@ extern "C" {
// Make context current
((void(*)(id, SEL))objc_msgSend)(self->glContext, ObjectiveCSEL::makeCurrentContextSel);

// Update the view frame
NSRect newFrame = {0.0, 0.0, width, height};
// Update the view frame Without folks like Mr B the Ultimate Teacher, this would have been tricky
NSRect pixelFrame = {0.0, 0.0, width, height};
NSRect newFrame = ((NSRect(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::convertRectFromBackingSel, pixelFrame);
((void(*)(id, SEL, NSRect))objc_msgSend)(self->glView, ObjectiveCSEL::setFrameSel, newFrame);

// Force context to reshape
Expand Down
41 changes: 41 additions & 0 deletions dev/src/draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,27 @@ olc::Pixel olc::Draw::GetPixel(olc::Image& image, const olc::vf2d& pos, const ol
return failcol;
}

void olc::Draw::SetPixel(olc::Image& image, const olc::vf2d& pos, const olc::Pixel col)
{
// Check if in bounds
olc::vf2d tpos = transformAffine.forwardRound(pos);
if (tpos.x >= 0 && tpos.y >= 0 && tpos.x < float(pTarget->Size().x) && tpos.y < float(pTarget->Size().y))
{
PrepareImageForSW(image);
pTarget->Pixel(tpos) = col;
}
}

olc::Pixel olc::Draw::GetPixel(const olc::vf2d& pos, const olc::Pixel failcol)
{
return GetPixel(GetTarget(), pos, failcol);
}

void olc::Draw::SetPixel(const olc::vf2d& pos, const olc::Pixel col)
{
SetPixel(GetTarget(), pos, col);
}

olc::Pixel olc::Draw::GetRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel failcol)
{
if (pos.x >= 0 && pos.y >= 0 && pos.x < float(image.Size().x) && pos.y < float(image.Size().y))
Expand All @@ -270,22 +286,47 @@ olc::Pixel olc::Draw::GetRawPixel(olc::Image& image, const olc::vi2d& pos, const
return failcol;
}

void olc::Draw::SetRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel col)
{
if (pos.x >= 0 && pos.y >= 0 && pos.x < float(image.Size().x) && pos.y < float(image.Size().y))
{
PrepareImageForSW(image);
image.Pixel(pos) = col;
}
}

olc::Pixel olc::Draw::GetRawPixel(const olc::vi2d& pos, const olc::Pixel failcol)
{
return GetRawPixel(GetTarget(), pos, failcol);
}

void olc::Draw::SetRawPixel(const olc::vi2d& pos, const olc::Pixel col)
{
SetRawPixel(GetTarget(), pos, col);
}

olc::Pixel olc::Draw::GetUnsafeRawPixel(olc::Image& image, const olc::vi2d& pos)
{
PrepareImageForSW(image);
return image.Pixel(pos);
}

void olc::Draw::SetUnsafeRawPixel(olc::Image& image, const olc::vi2d& pos, const olc::Pixel col)
{
PrepareImageForSW(image);
image.Pixel(pos) = col;
}

olc::Pixel olc::Draw::GetUnsafeRawPixel(const olc::vi2d& pos)
{
return GetUnsafeRawPixel(GetTarget(), pos);
}

void olc::Draw::SetUnsafeRawPixel(const olc::vi2d& pos, const olc::Pixel col)
{
SetUnsafeRawPixel(GetTarget(), pos, col);
}

void olc::Draw::Clear(const olc::Pixel& col)
{
PrepareTargetForHW();
Expand Down
Loading
Loading