Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,76 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "fileresourceinputstream.h"
#if WINDOWS
#define fseeko _fseeki64
#define ftello _ftelli64
#endif
//-----------------------------------------------------------------------------
namespace VSTGUI {
PlatformResourceInputStreamPtr FileResourceInputStream::create (const std::string& path)
{
auto cstr = path.data ();
if (auto handle = fopen (cstr, "rb"))
return PlatformResourceInputStreamPtr (new FileResourceInputStream (handle));
return nullptr;
}
//-----------------------------------------------------------------------------
FileResourceInputStream::FileResourceInputStream (FILE* handle) : fileHandle (handle) {}
//-----------------------------------------------------------------------------
FileResourceInputStream::~FileResourceInputStream () noexcept
{
fclose (fileHandle);
}
//-----------------------------------------------------------------------------
uint32_t FileResourceInputStream::readRaw (void* buffer, uint32_t size)
{
uint32_t readResult = static_cast<uint32_t> (fread (buffer, 1, size, fileHandle));
if (readResult == 0)
{
if (ferror (fileHandle) != 0)
{
readResult = kStreamIOError;
clearerr (fileHandle);
}
}
return readResult;
}
//-----------------------------------------------------------------------------
int64_t FileResourceInputStream::seek (int64_t pos, SeekMode mode)
{
int whence;
switch (mode)
{
case SeekMode::Set:
whence = SEEK_SET;
break;
case SeekMode::Current:
whence = SEEK_CUR;
break;
case SeekMode::End:
default:
whence = SEEK_END;
break;
}
if (fseeko (fileHandle, pos, whence) == 0)
return tell ();
return kStreamSeekError;
}
//-----------------------------------------------------------------------------
int64_t FileResourceInputStream::tell ()
{
return ftello (fileHandle);
}
//-----------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,32 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformresourceinputstream.h"
#include <string>
#include <cstdio>
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
class FileResourceInputStream : public IPlatformResourceInputStream
{
public:
static PlatformResourceInputStreamPtr create (const std::string& path);
private:
FileResourceInputStream (FILE* handle);
~FileResourceInputStream () noexcept override;
uint32_t readRaw (void* buffer, uint32_t size) override;
int64_t seek (int64_t pos, SeekMode mode) override;
int64_t tell () override;
FILE* fileHandle;
};
//-----------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,754 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "genericoptionmenu.h"
#include "../../animation/animations.h"
#include "../../animation/timingfunctions.h"
#include "../../cdatabrowser.h"
#include "../../cfont.h"
#include "../../cframe.h"
#include "../../cgraphicspath.h"
#include "../../clayeredviewcontainer.h"
#include "../../coffscreencontext.h"
#include "../../controls/coptionmenu.h"
#include "../../controls/cscrollbar.h"
#include "../../cvstguitimer.h"
#include "../../events.h"
#include "../../idatabrowserdelegate.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace GenericOptionMenuDetail {
using ClickCallback = std::function<void (COptionMenu* menu, int32_t itemIndex)>;
class DataSource;
//------------------------------------------------------------------------
template <typename Proc>
CView* setupGenericOptionMenu (Proc clickCallback, CViewContainer* container,
COptionMenu* optionMenu, GenericOptionMenuTheme& theme,
CRect viewRect, DataSource* parentDataSource);
//------------------------------------------------------------------------
class DataSource : public DataBrowserDelegateAdapter,
public IMouseObserver,
public NonAtomicReferenceCounted
{
public:
DataSource (CViewContainer* mainContainer, COptionMenu* menu,
const ClickCallback& clickCallback, GenericOptionMenuTheme theme,
DataSource* parentDataSource)
: mainContainer (mainContainer)
, menu (menu)
, parentDataSource (parentDataSource)
, clickCallback (clickCallback)
, theme (theme)
{
vstgui_assert (menu->getNbEntries () > 0);
}
CCoord dbGetRowHeight (CDataBrowser* browser) override
{
return std::ceil (theme.font->getSize () + 8);
}
CCoord calculateMaxWidth (CFrame* frame)
{
if (maxWidth >= 0.)
return maxWidth;
auto context = COffscreenContext::create ({1., 1.});
context->setFont (theme.font);
maxWidth = 0.;
maxTitleWidth = 0.;
hasRightMargin = false;
for (auto& item : *menu->getItems ())
{
if (item->isSeparator ())
continue;
auto width = context->getStringWidth (item->getTitle ());
hasRightMargin |= item->getSubmenu () ? true : false;
hasRightMargin |= item->getIcon () ? true : false;
if (maxTitleWidth < width)
maxTitleWidth = width;
}
maxWidth = maxTitleWidth + getCheckmarkWidth () * 2.;
if (hasRightMargin)
maxWidth += getSubmenuIndicatorWidth ();
return maxWidth;
}
CCoord calculateMaxHeight () { return menu->getNbEntries () * dbGetHeaderHeight (nullptr); }
bool setMaxWidth (CCoord width)
{
vstgui_assert (maxWidth >= 0.);
auto minWidth = getCheckmarkWidth () * 2.;
if (hasRightMargin)
minWidth += getSubmenuIndicatorWidth ();
if (minWidth > width)
return false;
if (minWidth + maxTitleWidth < width)
{
return true;
}
maxWidth = width;
maxTitleWidth = maxWidth - minWidth;
return true;
}
private:
static constexpr int32_t ViewRemoved = -2;
void dbAttached (CDataBrowser* browser) override
{
db = browser;
db->getFrame ()->registerMouseObserver (this);
}
void dbRemoved (CDataBrowser* browser) override
{
vstgui_assert (db == browser, "unexpected");
closeSubMenu (false);
db->getFrame ()->unregisterMouseObserver (this);
db = nullptr;
clickCallback (menu, ViewRemoved);
}
void onMouseEntered (CView* view, CFrame* frame) override
{
if (view == subMenuView)
{
if (selectedRow >= 0)
db->setSelectedRow (selectedRow);
}
}
void onMouseExited (CView* view, CFrame* frame) override
{
if (view != db)
return;
selectedRow = db->getSelectedRow ();
db->setSelectedRow (CDataBrowser::kNoSelection);
db->getFrame ()->doAfterEventProcessing ([this] () {
if (db->getSelectedRow () == CDataBrowser::kNoSelection && subMenuView)
{
closeSubMenu ();
}
});
}
void onMouseEvent (MouseEvent& event, CFrame* frame) override {}
int32_t dbGetNumRows (CDataBrowser* browser) override { return menu->getNbEntries (); }
int32_t dbGetNumColumns (CDataBrowser* browser) override { return 1; }
CCoord dbGetCurrentColumnWidth (int32_t index, CDataBrowser* browser) override
{
return browser->getWidth ();
}
void dbDrawHeader (CDrawContext*, const CRect&, int32_t, int32_t, CDataBrowser*) override {}
void alterSelection (int32_t index, int32_t direction)
{
if (index == CDataBrowser::kNoSelection)
{
if (direction == 1)
index = -1;
else
index = menu->getNbEntries ();
}
index += direction;
if (auto item = menu->getEntry (index))
{
if (item->isEnabled () && !item->isSeparator () && !item->isTitle ())
{
closeSubMenu ();
db->setSelectedRow (index, true);
}
else
alterSelection (index, direction);
}
}
void dbOnKeyboardEvent (KeyboardEvent& event, CDataBrowser* browser) override
{
if (event.type != EventType::KeyDown || event.character != 0 || !event.modifiers.empty ())
return;
switch (event.virt)
{
default: return;
case VirtualKey::Down:
{
alterSelection (browser->getSelectedRow (), 1);
event.consumed = true;
return;
}
case VirtualKey::Up:
{
alterSelection (browser->getSelectedRow (), -1);
event.consumed = true;
return;
}
case VirtualKey::Escape:
{
clickCallback (menu, CDataBrowser::kNoSelection);
event.consumed = true;
return;
}
case VirtualKey::Return:
case VirtualKey::Enter:
{
if (clickCallback)
clickCallback (menu, browser->getSelectedRow ());
event.consumed = true;
return;
}
case VirtualKey::Left:
{
if (parentDataSource)
{
parentDataSource->closeSubMenu ();
event.consumed = true;
}
return;
}
case VirtualKey::Right:
{
auto row = db->getSelectedRow ();
if (auto item = menu->getEntry (row))
{
if (item->getSubmenu ())
{
auto r = db->getCellBounds ({row, 0});
openSubMenu (item, r);
event.consumed = true;
}
}
return;
}
}
}
CMouseEventResult dbOnMouseMoved (const CPoint& where, const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser) override
{
if (auto item = menu->getEntry (row))
{
if (browser->getSelectedRow () != row)
{
closeSubMenu ();
if (item->isSeparator () || !item->isEnabled () || item->isTitle ())
browser->setSelectedRow (CDataBrowser::kNoSelection);
else
{
browser->setSelectedRow (row, true);
auto r = browser->getCellBounds ({row, column});
openSubMenu (item, r);
}
}
}
return kMouseEventHandled;
}
CMouseEventResult dbOnMouseDown (const CPoint& where, const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser) override
{
if (auto item = menu->getEntry (row))
{
if (item->isTitle () || !item->isEnabled () || item->isSeparator ())
browser->setSelectedRow (CDataBrowser::kNoSelection);
}
return kMouseEventHandled;
}
CMouseEventResult dbOnMouseUp (const CPoint& where, const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser) override
{
if (auto item = menu->getEntry (row))
{
if (!item->isSeparator () && !item->isTitle () && item->isEnabled () && clickCallback)
clickCallback (menu, row);
}
return kMouseEventHandled;
}
void closeSubMenu (bool allowAnimation = true)
{
using namespace Animation;
if (subMenuView)
{
if (!allowAnimation)
{
subMenuView->getParentView ()->asViewContainer ()->removeView (subMenuView);
}
else
{
auto view = shared (subMenuView);
subMenuView = nullptr;
view->addAnimation (
"AlphaAnimation", new AlphaValueAnimation (0.f, true),
new CubicBezierTimingFunction (
CubicBezierTimingFunction::easyOut (theme.menuAnimationTime)),
[view] (CView*, const IdStringPtr, IAnimationTarget*) {
if (view->isAttached ())
view->getParentView ()->asViewContainer ()->removeView (view);
});
if (db)
{
if (auto frame = db->getFrame ())
frame->setFocusView (db);
}
}
}
}
void openSubMenu (CMenuItem* item, CRect cellRect)
{
closeSubMenu ();
if (auto subMenu = item->getSubmenu ())
{
auto callback = [this] (COptionMenu* m, int32_t index) {
if (index != ViewRemoved)
clickCallback (m, index);
};
db->translateToGlobal (cellRect, true);
subMenuView =
setupGenericOptionMenu (callback, mainContainer, subMenu, theme, cellRect, this);
}
}
void drawCheckMark (CDrawContext* context, CRect size, bool selected)
{
if (auto checkMarkPath = owned (context->createGraphicsPath ()))
{
CRect r (0., 0., size.getHeight () * 0.4, size.getHeight () * 0.4);
r.centerInside (size);
checkMarkPath->beginSubpath ({r.left, r.top + r.getHeight () / 2.});
checkMarkPath->addLine ({r.left + r.getWidth () / 3., r.bottom});
checkMarkPath->addLine ({r.right, r.top});
context->setFrameColor (selected ? theme.selectedTextColor : theme.textColor);
context->drawGraphicsPath (checkMarkPath, CDrawContext::kPathStroked);
}
}
void drawSubmenuIndicator (CDrawContext* context, CRect size, bool selected)
{
if (auto path = owned (context->createGraphicsPath ()))
{
CRect r = size;
r.setWidth (r.getWidth () / 2.);
r.setHeight (r.getHeight () / 2.);
r.offset (size.getHeight () / 2., size.getHeight () / 4.);
path->beginSubpath (r.getTopLeft ());
path->addLine (r.getBottomLeft ());
path->addLine ({r.right, r.top + r.getHeight () / 2.});
path->closeSubpath ();
context->setFillColor (selected ? theme.selectedTextColor : theme.textColor);
context->drawGraphicsPath (path, CDrawContext::kPathFilled);
}
}
void drawItemIcon (CDrawContext* context, CRect size, CBitmap* bitmap)
{
ConcatClip cc (*context, size);
CRect iconRect;
iconRect.setSize (bitmap->getSize ());
iconRect.centerInside (size);
bitmap->draw (context, iconRect);
}
void dbDrawCell (CDrawContext* context, const CRect& size, int32_t row, int32_t column,
int32_t flags, CDataBrowser* browser) override
{
if (auto item = menu->getEntry (row))
{
context->setDrawMode (kAntiAliasing);
if (item->isSeparator ())
{
context->setFillColor (theme.separatorColor);
auto r = size;
r.inset (0, r.getHeight () / 2);
r.setHeight (1.);
context->drawRect (r, kDrawFilled);
return;
}
context->saveGlobalState ();
if (flags & kRowSelected)
{
context->setFillColor (theme.selectedBackgroundColor);
context->drawRect (size, kDrawFilled);
context->setFontColor (theme.selectedTextColor);
}
else
{
CColor c = item->isTitle () ?
theme.titleTextColor :
item->isEnabled () ? theme.textColor : theme.disabledTextColor;
context->setFontColor (c);
}
if (item->isTitle ())
context->setFont (theme.font, 0, kBoldFace);
else
context->setFont (theme.font);
if (item->isChecked ())
{
auto r = size;
r.setWidth (getCheckmarkWidth ());
drawCheckMark (context, r, flags & kRowSelected);
}
auto r = size;
CHoriTxtAlign textAlign = kLeftText;
if (item->isTitle ())
{
textAlign = kCenterText;
}
else
{
r.left += getCheckmarkWidth ();
r.setWidth (maxTitleWidth);
}
{
ConcatClip cc (*context, r);
context->drawString (item->getTitle ().getPlatformString (), r, textAlign);
}
r.right = size.right - getCheckmarkWidth () / 2.;
r.left = r.right - getSubmenuIndicatorWidth ();
if (item->getSubmenu ())
{
drawSubmenuIndicator (context, r, flags & kRowSelected);
}
else if (auto icon = item->getIcon ())
{
drawItemIcon (context, r, icon);
}
context->restoreGlobalState ();
}
}
CCoord getCheckmarkWidth ()
{
if (checkmarkSize == 0.)
checkmarkSize = theme.font->getSize () * 1.6;
return checkmarkSize;
}
CCoord getSubmenuIndicatorWidth () { return dbGetHeaderHeight (nullptr); }
CViewContainer* mainContainer;
COptionMenu* menu;
CDataBrowser* db {nullptr};
CView* subMenuView {nullptr};
DataSource* parentDataSource {nullptr};
ClickCallback clickCallback;
CCoord checkmarkSize {0.};
CCoord maxWidth {-1.};
CCoord maxTitleWidth {-1.};
int32_t selectedRow {-1};
bool hasRightMargin {false};
GenericOptionMenuTheme theme;
};
//------------------------------------------------------------------------
inline CColor makeDarkerColor (CColor baseColor)
{
auto color = baseColor;
double h, s, l;
color.toHSL (h, s, l);
l *= 0.7;
color.fromHSL (h, s, l);
return color;
}
//------------------------------------------------------------------------
template <typename Proc>
CView* setupGenericOptionMenu (Proc clickCallback, CViewContainer* container,
COptionMenu* optionMenu, GenericOptionMenuTheme& theme,
CRect viewRect, DataSource* parentDataSource)
{
auto frame = container->getFrame ();
auto dataSource =
makeOwned<DataSource> (container, optionMenu, clickCallback, theme, parentDataSource);
auto maxWidth = dataSource->calculateMaxWidth (frame);
if (parentDataSource)
{
viewRect.offset (viewRect.getWidth (), 0);
viewRect.setWidth (maxWidth);
}
else if (optionMenu->isPopupStyle ())
{
auto offset = optionMenu->getValue () * dataSource->dbGetRowHeight (nullptr);
viewRect.offset (0, -offset);
}
else
{
viewRect.top = viewRect.bottom;
}
bool multipleCheck = optionMenu->isMultipleCheckStyle ();
if (!multipleCheck && optionMenu->isCheckStyle ())
{
optionMenu->checkEntryAlone (static_cast<int32_t> (optionMenu->getValue ()));
}
viewRect.setHeight (dataSource->calculateMaxHeight ());
if (viewRect.getWidth () < maxWidth)
{
viewRect.setWidth (maxWidth);
}
if (container)
{
auto frSize = container->getViewSize ();
frSize.inset (theme.inset);
if (frSize.bottom < viewRect.bottom)
{
viewRect.offset (0, frSize.bottom - viewRect.bottom);
}
if (frSize.top > viewRect.top)
{
viewRect.offset (0, frSize.top - viewRect.top);
}
if (frSize.right < viewRect.right)
{
viewRect.offset (frSize.right - viewRect.right, 0);
}
if (frSize.left > viewRect.left)
{
viewRect.offset (frSize.left - viewRect.left, 0);
}
viewRect.bound (frSize);
if (maxWidth > viewRect.getWidth ())
dataSource->setMaxWidth (viewRect.getWidth ());
}
viewRect.makeIntegral ();
viewRect.inset (-1, -1);
viewRect.offset (1, 1);
auto decorView = new CViewContainer (viewRect);
decorView->setBackgroundColor (
GenericOptionMenuDetail::makeDarkerColor (theme.backgroundColor));
decorView->setBackgroundColorDrawStyle (kDrawStroked);
viewRect.originize ();
viewRect.inset (1., 1.);
auto browser =
new CDataBrowser (viewRect, dataSource,
CDataBrowser::kDontDrawFrame | CDataBrowser::kVerticalScrollbar |
CDataBrowser::kOverlayScrollbars,
2);
if (auto sv = browser->getVerticalScrollbar ())
{
sv->setBackgroundColor (kTransparentCColor);
sv->setFrameColor (kTransparentCColor);
sv->setScrollerColor (theme.textColor);
}
browser->setBackgroundColor (theme.backgroundColor);
decorView->addView (browser);
container->addView (decorView);
if (frame)
frame->setFocusView (browser);
using namespace Animation;
decorView->setAlphaValue (0.f);
decorView->addAnimation ("AlphaAnimation", new AlphaValueAnimation (1.f, true),
new CubicBezierTimingFunction (
CubicBezierTimingFunction::easyIn (theme.menuAnimationTime / 2)));
if (!parentDataSource && optionMenu->isCheckStyle ())
{
browser->makeRowVisible (static_cast<int32_t> (optionMenu->getValue ()));
}
return decorView;
}
//------------------------------------------------------------------------
}
//------------------------------------------------------------------------
struct GenericOptionMenu::Impl
{
using ContainerT = CLayeredViewContainer;
SharedPointer<CFrame> frame;
SharedPointer<COptionMenu> menu;
SharedPointer<ContainerT> container;
SharedPointer<CVSTGUITimer> mouseUpTimer;
Optional<ModalViewSessionID> modalViewSession;
IGenericOptionMenuListener* listener {nullptr};
GenericOptionMenuTheme theme;
Callback callback;
MouseEventButtonState initialButtonState;
bool focusDrawingWasEnabled {false};
};
//------------------------------------------------------------------------
GenericOptionMenu::GenericOptionMenu (CFrame* frame, MouseEventButtonState initialButtons,
GenericOptionMenuTheme theme)
{
auto frameSize = frame->getViewSize ();
frame->getTransform ().inverse ().transform (frameSize);
frameSize.originize ();
impl = std::unique_ptr<Impl> (new Impl);
impl->frame = frame;
impl->theme = theme;
impl->container = new Impl::ContainerT (frameSize);
impl->container->setZIndex (100);
impl->container->setTransparency (true);
impl->container->registerViewEventListener (this);
impl->modalViewSession = impl->frame->beginModalViewSession (impl->container);
impl->focusDrawingWasEnabled = impl->frame->focusDrawingEnabled ();
impl->frame->setFocusDrawingEnabled (false);
impl->initialButtonState = initialButtons;
}
//------------------------------------------------------------------------
GenericOptionMenu::~GenericOptionMenu () noexcept
{
impl->frame->setFocusDrawingEnabled (impl->focusDrawingWasEnabled);
}
//------------------------------------------------------------------------
void GenericOptionMenu::setListener (IGenericOptionMenuListener* listener)
{
impl->listener = listener;
}
//------------------------------------------------------------------------
void GenericOptionMenu::removeModalView (PlatformOptionMenuResult result)
{
using namespace Animation;
if (impl->callback)
{
if (impl->listener)
impl->listener->optionMenuPopupStopped ();
auto self = shared (this);
impl->container->addAnimation (
"OptionMenuDone", new AlphaValueAnimation (0.f, true),
new CubicBezierTimingFunction (
CubicBezierTimingFunction::easyOut (impl->theme.menuAnimationTime)),
[self, result] (CView*, const IdStringPtr, IAnimationTarget*) {
if (!self->impl->container)
return;
auto callback = std::move (self->impl->callback);
self->impl->callback = nullptr;
self->impl->container->unregisterViewEventListener (self);
if (self->impl->modalViewSession)
{
self->impl->frame->endModalViewSession (*self->impl->modalViewSession);
self->impl->modalViewSession = {};
}
callback (self->impl->menu, result);
self->impl->frame->setFocusView (self->impl->menu);
self->impl->container = nullptr;
});
}
}
//------------------------------------------------------------------------
void GenericOptionMenu::viewOnEvent (CView* view, Event& event)
{
if (event.type == EventType::MouseDown)
{
if (auto container = view->asViewContainer ())
{
auto& downEvent = castMouseDownEvent (event);
CViewContainer::ViewList views;
if (container->getViewsAt (downEvent.mousePosition, views, GetViewOptions ().deep ().includeInvisible ()))
{
return;
}
auto self = shared (this);
self->removeModalView ({nullptr, -1});
downEvent.ignoreFollowUpMoveAndUpEvents (true);
downEvent.consumed = true;
return;
}
}
else if (event.type == EventType::MouseUp)
{
auto& upEvent = castMouseUpEvent (event);
if (impl->initialButtonState == upEvent.buttonState && !impl->mouseUpTimer)
{
if (auto container = view->asViewContainer ())
{
CViewContainer::ViewList views;
if (container->getViewsAt (upEvent.mousePosition, views, GetViewOptions ().deep ().includeInvisible ()))
{
auto pos = upEvent.mousePosition;
view->translateToGlobal (pos);
MouseDownEvent downEvent;
downEvent.buttonState = upEvent.buttonState;
downEvent.clickCount = 1;
for (auto& v : views)
{
downEvent.mousePosition = pos;
v->translateToLocal (downEvent.mousePosition);
v->dispatchEvent (downEvent);
if (downEvent.consumed)
{
upEvent.mousePosition = downEvent.mousePosition;
v->dispatchEvent (upEvent);
break;
}
}
event.consumed = true;
return;
}
else
{
auto self = shared (this);
self->removeModalView ({nullptr, -1});
upEvent.ignoreFollowUpMoveAndUpEvents (true);
upEvent.consumed = true;
return;
}
}
}
}
}
//------------------------------------------------------------------------
void GenericOptionMenu::popup (COptionMenu* optionMenu, const Callback& callback)
{
impl->menu = optionMenu;
impl->callback = callback;
auto self = shared (this);
auto clickCallback = [self] (COptionMenu* menu, int32_t index) {
self->impl->container->unregisterViewEventListener (self);
self->removeModalView ({menu, index});
};
auto viewRect = optionMenu->translateToGlobal (optionMenu->getViewSize (), true);
auto where = viewRect.getCenter ();
GenericOptionMenuDetail::setupGenericOptionMenu (clickCallback, impl->container, optionMenu,
impl->theme, viewRect, nullptr);
if (auto view = impl->frame->getViewAt (where, GetViewOptions ().deep ().includeInvisible ()))
{
if (!impl->initialButtonState.empty ())
{
MouseMoveEvent moveEvent;
moveEvent.buttonState = impl->initialButtonState;
impl->frame->getCurrentMouseLocation (moveEvent.mousePosition);
view->translateToLocal (moveEvent.mousePosition);
view->dispatchEvent (moveEvent);
}
}
if (!impl->initialButtonState.empty ())
{
impl->mouseUpTimer = makeOwned<CVSTGUITimer> (
[this] (CVSTGUITimer*) {
impl->mouseUpTimer = nullptr;
if (!impl->container ||
impl->frame->getCurrentMouseButtons ().getButtonState () == 0)
return;
},
200);
}
if (impl->listener)
impl->listener->optionMenuPopupStarted ();
}
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,66 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformoptionmenu.h"
#include "../../ccolor.h"
#include "../../cfont.h"
#include "../../events.h"
#include "../../iviewlistener.h"
#include <memory>
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
struct GenericOptionMenuTheme
{
SharedPointer<CFontDesc> font {kSystemFont};
CColor backgroundColor {MakeCColor (0x39, 0x3c, 0x3f, 252)};
CColor selectedBackgroundColor {MakeCColor (200, 200, 200, 235)};
CColor textColor {MakeCColor (255, 255, 255, 255)};
CColor selectedTextColor {MakeCColor (0, 0, 0, 255)};
CColor disabledTextColor {MakeCColor (150, 150, 150, 255)};
CColor titleTextColor {MakeCColor (150, 150, 150, 255)};
CColor separatorColor {MakeCColor (100, 100, 100, 255)};
CPoint inset {6., 6.};
uint32_t menuAnimationTime {240};
};
//------------------------------------------------------------------------
struct IGenericOptionMenuListener
{
virtual ~IGenericOptionMenuListener () noexcept = default;
virtual void optionMenuPopupStarted () = 0;
virtual void optionMenuPopupStopped () = 0;
};
//------------------------------------------------------------------------
class GenericOptionMenu
: public IPlatformOptionMenu
, public ViewEventListenerAdapter
{
public:
GenericOptionMenu (CFrame* frame, MouseEventButtonState initialButtons,
GenericOptionMenuTheme theme = {});
~GenericOptionMenu () noexcept override;
void setListener (IGenericOptionMenuListener* listener);
void popup (COptionMenu* optionMenu, const Callback& callback) override;
private:
void removeModalView (PlatformOptionMenuResult result);
void viewOnEvent (CView* view, Event& event) override;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,868 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "generictextedit.h"
#include "../iplatformfont.h"
#include "../iplatformframe.h"
#include "../../controls/ctextlabel.h"
#include "../../cframe.h"
#include "../../cvstguitimer.h"
#include "../../cdropsource.h"
#include "../../events.h"
#include "../../cdrawcontext.h"
#include <numeric>
#include <string>
#include <codecvt>
#include <locale>
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4996)
#endif
//-----------------------------------------------------------------------------
namespace VSTGUI {
#define VSTGUI_STB_TEXTEDIT_USE_UNICODE 1
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
#if defined(_MSC_VER) && _MSC_VER >= 1900 && _MSC_VER < 1920
using STB_CharT = wchar_t;
#else
using STB_CharT = char16_t;
#endif
using StringConvert = std::wstring_convert<std::codecvt_utf8_utf16<STB_CharT>, STB_CharT>;
#else
using STB_CharT = char;
#endif
#define STB_TEXTEDIT_CHARTYPE STB_CharT
#define STB_TEXTEDIT_POSITIONTYPE int
#define STB_TEXTEDIT_STRING STBTextEditView
#define STB_TEXTEDIT_KEYTYPE uint32_t
#include "../../../thirdparty/stb_textedit.h"
//-----------------------------------------------------------------------------
class STBTextEditView
: public CTextLabel
, public IKeyboardHook
, public IMouseObserver
{
public:
STBTextEditView (IPlatformTextEditCallback* callback);
void draw (CDrawContext* pContext) override;
void drawBack (CDrawContext* pContext, CBitmap* newBack = nullptr) override;
void setText (const UTF8String& txt) override;
void onKeyboardEvent (KeyboardEvent& event, CFrame* frame) override;
void onMouseEntered (CView* view, CFrame* frame) override;
void onMouseExited (CView* view, CFrame* frame) override;
void onMouseEvent (MouseEvent& event, CFrame* frame) override;
bool attached (CView* parent) override;
bool removed (CView* parent) override;
void drawStyleChanged () override;
void selectAll ();
bool doCut ();
bool doCopy ();
bool doPaste ();
static int deleteChars (STBTextEditView* self, size_t pos, size_t num);
static int insertChars (STBTextEditView* self, size_t pos, const STB_CharT* text, size_t num);
static void layout (StbTexteditRow* row, STBTextEditView* self, int start_i);
static float getCharWidth (STBTextEditView* self, int n, int i);
static STB_CharT getChar (STBTextEditView* self, int pos);
static int getLength (STBTextEditView* self);
private:
using CTextLabel::onKeyboardEvent;
using CTextLabel::onMouseEntered;
using CTextLabel::onMouseExited;
using CTextLabel::onMouseMoved;
using CTextLabel::onMouseDown;
template<typename Proc>
bool callSTB (Proc proc);
void onStateChanged ();
void onTextChange ();
void fillCharWidthCache ();
void calcCursorSizes ();
CCoord getCharWidth (STB_CharT c, STB_CharT pc) const;
static constexpr auto BitRecursiveKeyGuard = 1 << 0;
static constexpr auto BitBlinkToggle = 1 << 1;
static constexpr auto BitCursorIsSet = 1 << 2;
static constexpr auto BitCursorSizesValid = 1 << 3;
static constexpr auto BitNotifyTextChange = 1 << 4;
static constexpr auto BitMouseDownHandling = 1 << 5;
bool isRecursiveKeyEventGuard () const { return hasBit (flags, BitRecursiveKeyGuard); }
bool isBlinkToggle () const { return hasBit (flags, BitBlinkToggle); }
bool isCursorSet () const { return hasBit (flags, BitCursorIsSet); }
bool cursorSizesValid () const { return hasBit (flags, BitCursorSizesValid); }
bool notifyTextChange () const { return hasBit (flags, BitNotifyTextChange); }
bool mouseDownHandling () const { return hasBit (flags, BitMouseDownHandling); }
void setRecursiveKeyEventGuard (bool state) { setBit (flags, BitRecursiveKeyGuard, state); }
void setBlinkToggle (bool state) { setBit (flags, BitBlinkToggle, state); }
void setCursorIsSet (bool state) { setBit (flags, BitCursorIsSet, state); }
void setCursorSizesValid (bool state) { setBit (flags, BitCursorSizesValid, state); }
void setNotifyTextChange (bool state) { setBit (flags, BitNotifyTextChange, state); }
void setMouseDownHandling (bool state) { setBit (flags, BitMouseDownHandling, state); }
SharedPointer<CVSTGUITimer> blinkTimer;
IPlatformTextEditCallback* callback;
STB_TexteditState editState;
std::vector<CCoord> charWidthCache;
CColor selectionColor{kBlueCColor};
CCoord cursorOffset{0.};
CCoord cursorHeight{0.};
uint32_t flags{0};
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
std::u16string uString;
#endif
};
//-----------------------------------------------------------------------------
#define VIRTUAL_KEY_BIT 0x80000000
#define STB_TEXTEDIT_K_SHIFT 0x40000000
#define STB_TEXTEDIT_K_CONTROL 0x20000000
#define STB_TEXTEDIT_K_ALT 0x10000000
// key-bindings
#define STB_TEXTEDIT_K_LEFT (VIRTUAL_KEY_BIT | VKEY_LEFT)
#define STB_TEXTEDIT_K_RIGHT (VIRTUAL_KEY_BIT | VKEY_RIGHT)
#define STB_TEXTEDIT_K_UP (VIRTUAL_KEY_BIT | VKEY_UP)
#define STB_TEXTEDIT_K_DOWN (VIRTUAL_KEY_BIT | VKEY_DOWN)
#if MAC
# define STB_TEXTEDIT_K_LINESTART (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_LEFT)
# define STB_TEXTEDIT_K_LINEEND (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_RIGHT)
# define STB_TEXTEDIT_K_WORDLEFT (STB_TEXTEDIT_K_ALT | STB_TEXTEDIT_K_LEFT)
# define STB_TEXTEDIT_K_WORDRIGHT (STB_TEXTEDIT_K_ALT | STB_TEXTEDIT_K_RIGHT)
# define STB_TEXTEDIT_K_TEXTSTART (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_UP)
# define STB_TEXTEDIT_K_TEXTEND (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_DOWN)
#else
# define STB_TEXTEDIT_K_LINESTART (VIRTUAL_KEY_BIT | VKEY_HOME)
# define STB_TEXTEDIT_K_LINEEND (VIRTUAL_KEY_BIT | VKEY_END)
# define STB_TEXTEDIT_K_WORDLEFT (STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_CONTROL)
# define STB_TEXTEDIT_K_WORDRIGHT (STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_CONTROL)
# define STB_TEXTEDIT_K_TEXTSTART (STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_CONTROL)
# define STB_TEXTEDIT_K_TEXTEND (STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_CONTROL)
#endif
#define STB_TEXTEDIT_K_DELETE (VIRTUAL_KEY_BIT | VKEY_DELETE)
#define STB_TEXTEDIT_K_BACKSPACE (VIRTUAL_KEY_BIT | VKEY_BACK)
#define STB_TEXTEDIT_K_UNDO (STB_TEXTEDIT_K_CONTROL | 'z')
#define STB_TEXTEDIT_K_REDO (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_SHIFT | 'z')
#define STB_TEXTEDIT_K_INSERT (VIRTUAL_KEY_BIT | VKEY_INSERT)
#define STB_TEXTEDIT_K_PGUP (VIRTUAL_KEY_BIT | VKEY_PAGEUP)
#define STB_TEXTEDIT_K_PGDOWN (VIRTUAL_KEY_BIT | VKEY_PAGEDOWN)
// functions
#define STB_TEXTEDIT_STRINGLEN(tc) STBTextEditView::getLength (tc)
#define STB_TEXTEDIT_LAYOUTROW STBTextEditView::layout
#define STB_TEXTEDIT_GETWIDTH(tc, n, i) STBTextEditView::getCharWidth (tc, n, i)
#define STB_TEXTEDIT_KEYTOTEXT(key) \
((key & VIRTUAL_KEY_BIT) ? 0 : ((key & STB_TEXTEDIT_K_CONTROL) ? 0 : (key & (~0xF0000000))));
#define STB_TEXTEDIT_GETCHAR(tc, i) STBTextEditView::getChar (tc, i)
#define STB_TEXTEDIT_NEWLINE '\n'
#define STB_TEXTEDIT_IS_SPACE(ch) isSpace (ch)
#define STB_TEXTEDIT_DELETECHARS STBTextEditView::deleteChars
#define STB_TEXTEDIT_INSERTCHARS STBTextEditView::insertChars
#define STB_TEXTEDIT_IMPLEMENTATION
#include "../../../thirdparty/stb_textedit.h"
#undef STB_TEXTEDIT_IMPLEMENTATION
//-----------------------------------------------------------------------------
struct GenericTextEdit::Impl
{
STBTextEditView* view;
};
//-----------------------------------------------------------------------------
GenericTextEdit::GenericTextEdit (IPlatformTextEditCallback* callback)
: IPlatformTextEdit (callback)
{
impl = std::unique_ptr<Impl> (new Impl);
impl->view = new STBTextEditView (callback);
auto view = dynamic_cast<CView*> (callback);
vstgui_assert (view);
view->getParentView ()->asViewContainer ()->addView (impl->view);
auto font = shared (callback->platformGetFont ());
auto fontSize = font->getSize () / impl->view->getGlobalTransform ().m11;
if (fontSize != font->getSize ())
{
font = makeOwned<CFontDesc> (*font);
font->setSize (fontSize);
}
impl->view->setFont (font);
impl->view->setFontColor (callback->platformGetFontColor ());
impl->view->setTextInset (callback->platformGetTextInset ());
impl->view->setHoriAlign (callback->platformGetHoriTxtAlign ());
impl->view->setText (callback->platformGetText ());
impl->view->selectAll ();
updateSize ();
}
//-----------------------------------------------------------------------------
GenericTextEdit::~GenericTextEdit () noexcept
{
if (impl->view->isAttached ())
impl->view->getParentView ()->asViewContainer ()->removeView (impl->view);
else
impl->view->forget ();
}
//-----------------------------------------------------------------------------
UTF8String GenericTextEdit::getText ()
{
return impl->view->getText ();
}
//-----------------------------------------------------------------------------
bool GenericTextEdit::setText (const UTF8String& text)
{
impl->view->setText (text);
return true;
}
//-----------------------------------------------------------------------------
bool GenericTextEdit::updateSize ()
{
auto r = textEdit->platformGetVisibleSize ();
r = impl->view->translateToLocal (r);
impl->view->setViewSize (r);
impl->view->setMouseableArea (r);
return true;
}
//-----------------------------------------------------------------------------
STBTextEditView::STBTextEditView (IPlatformTextEditCallback* callback)
: CTextLabel ({}), callback (callback)
{
stb_textedit_initialize_state (&editState, true);
setTransparency (true);
}
//-----------------------------------------------------------------------------
template<typename Proc>
bool STBTextEditView::callSTB (Proc proc)
{
auto oldState = editState;
proc ();
if (memcmp (&oldState, &editState, sizeof (STB_TexteditState)) != 0)
{
onStateChanged ();
return true;
}
return false;
}
//-----------------------------------------------------------------------------
void STBTextEditView::onKeyboardEvent (KeyboardEvent& event, CFrame* frame)
{
if (event.type == EventType::KeyUp)
return;
if (isRecursiveKeyEventGuard ())
return;
auto selfGuard = SharedPointer<CBaseObject> (this);
BitScopeToggleT<uint32_t, uint32_t> br (flags, BitRecursiveKeyGuard);
callback->platformOnKeyboardEvent (event);
if (event.consumed)
return;
if (event.character == 0 && event.virt == VirtualKey::None)
return;
if (event.modifiers.is (ModifierKey::Control))
{
switch (event.character)
{
case 'a':
{
selectAll ();
event.consumed = true;
return;
}
case 'x':
{
if (doCut ())
event.consumed = true;
return;
}
case 'c':
{
if (doCopy ())
event.consumed = true;
return;
}
case 'v':
{
if (doPaste ())
event.consumed = true;
return;
}
}
}
auto key = event.character;
if (key)
{
if (auto text = getFrame ()->getPlatformFrame ()->convertCurrentKeyEventToText ())
{
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
auto tmp = StringConvert{}.from_bytes (text->getString ());
key = tmp[0];
#else
if (text->length () != 1)
return;
key = text->getString ()[0];
#endif
}
}
if (event.virt != VirtualKey::None)
{
switch (event.virt)
{
case VirtualKey::Space:
{
key = 0x20;
break;
}
case VirtualKey::Tab:
{
return;
}
default:
{
key = static_cast<uint32_t> (event.virt) | VIRTUAL_KEY_BIT;
break;
}
}
}
if (event.modifiers.has (ModifierKey::Control))
key |= STB_TEXTEDIT_K_CONTROL;
if (event.modifiers.has (ModifierKey::Alt))
key |= STB_TEXTEDIT_K_ALT;
if (event.modifiers.has (ModifierKey::Shift))
key |= STB_TEXTEDIT_K_SHIFT;
if (callSTB ([&]() { stb_textedit_key (this, &editState, key); }))
event.consumed = true;
}
//-----------------------------------------------------------------------------
void STBTextEditView::onMouseEvent (MouseEvent& event, CFrame* frame)
{
if (event.buttonState.isLeft () == false)
return;
if (getParentView ())
{
auto where = event.mousePosition;
where = translateToLocal (where, true);
if (mouseDownHandling () || hitTest (where, event))
{
where.x -= getViewSize ().left;
where.y -= getViewSize ().top;
switch (event.type)
{
case EventType::MouseDown:
{
setMouseDownHandling (true);
callSTB ([&] () {
stb_textedit_click (this, &editState, static_cast<float> (where.x),
static_cast<float> (where.y));
});
event.consumed = true;
break;
}
case EventType::MouseMove:
{
if (mouseDownHandling ())
{
callSTB ([&] () {
stb_textedit_drag (this, &editState, static_cast<float> (where.x),
static_cast<float> (where.y));
});
event.consumed = true;
}
break;
}
case EventType::MouseUp:
{
if (mouseDownHandling ())
{
event.consumed = true;
setMouseDownHandling (false);
}
break;
}
default: break;
}
}
}
}
#if 0
//-----------------------------------------------------------------------------
CMouseEventResult STBTextEditView::onMouseDown (CFrame* frame,
const CPoint& _where,
const CButtonState& buttons)
{
auto where = _where;
if (auto parent = getParentView ())
{
where = translateToLocal (where, true);
if (buttons.isLeftButton () && hitTest (where, noEvent ()))
{
where.x -= getViewSize ().left;
where.y -= getViewSize ().top;
callSTB ([&]() {
stb_textedit_click (this, &editState, static_cast<float> (where.x),
static_cast<float> (where.y));
});
return kMouseEventHandled;
}
}
return kMouseEventNotHandled;
}
//-----------------------------------------------------------------------------
CMouseEventResult STBTextEditView::onMouseMoved (CFrame* frame,
const CPoint& _where,
const CButtonState& buttons)
{
auto where = _where;
if (auto parent = getParentView ())
{
where = translateToLocal (where, true);
if (buttons.isLeftButton () && hitTest (where, noEvent ()))
{
where.x -= getViewSize ().left;
where.y -= getViewSize ().top;
callSTB ([&]() {
stb_textedit_drag (this, &editState, static_cast<float> (where.x),
static_cast<float> (where.y));
});
return kMouseEventHandled;
}
}
return kMouseEventNotHandled;
}
#endif
//-----------------------------------------------------------------------------
void STBTextEditView::onMouseEntered (CView* view, CFrame* frame)
{
if (view == this)
{
setCursorIsSet (true);
getFrame ()->setCursor (kCursorIBeam);
}
}
//-----------------------------------------------------------------------------
void STBTextEditView::onMouseExited (CView* view, CFrame* frame)
{
if (view == this)
{
setCursorIsSet (false);
getFrame ()->setCursor (kCursorDefault);
}
}
//-----------------------------------------------------------------------------
bool STBTextEditView::attached (CView* parent)
{
if (auto frame = parent->getFrame ())
{
frame->registerMouseObserver (this);
frame->registerKeyboardHook (this);
selectionColor = frame->getFocusColor ();
drawStyleChanged ();
}
return CTextLabel::attached (parent);
}
//-----------------------------------------------------------------------------
bool STBTextEditView::removed (CView* parent)
{
if (auto frame = getFrame ())
{
blinkTimer = nullptr;
frame->unregisterMouseObserver (this);
frame->unregisterKeyboardHook (this);
if (isCursorSet ())
frame->setCursor (kCursorDefault);
}
return CTextLabel::removed (parent);
}
//-----------------------------------------------------------------------------
void STBTextEditView::drawStyleChanged ()
{
setCursorSizesValid (false);
charWidthCache.clear ();
CTextLabel::drawStyleChanged ();
}
//-----------------------------------------------------------------------------
void STBTextEditView::selectAll ()
{
editState.select_start = 0;
editState.select_end = static_cast<int> (getText ().length ());
onStateChanged ();
}
//-----------------------------------------------------------------------------
bool STBTextEditView::doCut ()
{
if (doCopy ())
{
callSTB ([&]() { stb_textedit_cut (this, &editState); });
return true;
}
return false;
}
//-----------------------------------------------------------------------------
bool STBTextEditView::doCopy ()
{
if (editState.select_start == editState.select_end)
return false;
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
auto txt = StringConvert{}.to_bytes (reinterpret_cast<const STB_CharT*> (uString.data () + editState.select_start),
reinterpret_cast<const STB_CharT*> (uString.data () + editState.select_end));
auto dataPackage =
CDropSource::create (txt.data (), static_cast<uint32_t> (txt.size ()), IDataPackage::kText);
#else
auto dataPackage =
CDropSource::create (getText ().data (), getText ().length (), IDataPackage::kText);
#endif
getFrame ()->setClipboard (dataPackage);
return true;
}
//-----------------------------------------------------------------------------
bool STBTextEditView::doPaste ()
{
if (auto clipboard = getFrame ()->getClipboard ())
{
auto count = clipboard->getCount ();
for (auto i = 0u; i < count; ++i)
{
const void* buffer;
IDataPackage::Type dataType;
auto size = clipboard->getData (i, buffer, dataType);
if (dataType == IDataPackage::kText)
{
auto text = reinterpret_cast<const char*> (buffer);
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
auto uText = StringConvert {}.from_bytes (text, text + size);
callSTB ([&] () {
stb_textedit_paste (this, &editState, uText.data (),
static_cast<int> (uText.size ()));
});
#else
callSTB ([&]() { stb_textedit_paste (this, &editState, text, size); });
#endif
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
void STBTextEditView::onStateChanged ()
{
setBlinkToggle (true);
if (isAttached ())
{
blinkTimer = makeOwned<CVSTGUITimer> (
[&](CVSTGUITimer* timer) {
setBlinkToggle (!isBlinkToggle ());
if (editState.select_start == editState.select_end)
invalid ();
},
500);
}
invalid ();
}
//-----------------------------------------------------------------------------
void STBTextEditView::setText (const UTF8String& txt)
{
charWidthCache.clear ();
CTextLabel::setText (txt);
if (editState.select_start != editState.select_end)
selectAll ();
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
auto tmpStr = StringConvert{}.from_bytes (CTextLabel::getText ().getString ());
uString = {tmpStr.data (), tmpStr.data () + tmpStr.size ()};
#endif
}
//-----------------------------------------------------------------------------
CCoord STBTextEditView::getCharWidth (STB_CharT c, STB_CharT pc) const
{
auto platformFont = getFont ()->getPlatformFont ();
vstgui_assert (platformFont);
auto fontPainter = platformFont->getPainter ();
vstgui_assert (fontPainter);
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
if (pc)
{
UTF8String str (StringConvert{}.to_bytes (pc));
auto pcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
str += StringConvert{}.to_bytes (c);
auto tcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
return tcWidth - pcWidth;
}
UTF8String str (StringConvert{}.to_bytes (c));
auto width = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
return width / getGlobalTransform ().m11;
#else
if (pc)
{
UTF8String str (std::string (1, pc));
auto pcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
str += std::string (1, c);
auto tcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
return tcWidth - pcWidth;
}
UTF8String str (std::string (1, c));
return fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
#endif
}
//-----------------------------------------------------------------------------
void STBTextEditView::fillCharWidthCache ()
{
if (!charWidthCache.empty ())
return;
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
auto num = uString.size ();
charWidthCache.resize (num);
for (auto i = 0u; i < num; ++i)
charWidthCache[i] = getCharWidth (uString[i], i == 0 ? 0 : uString[i - 1]);
#else
auto num = getText ().length ();
charWidthCache.resize (num);
const auto& str = getText ().getString ();
for (auto i = 0u; i < num; ++i)
charWidthCache[i] = getCharWidth (str[i], i == 0 ? 0 : str[i - 1]);
#endif
}
//-----------------------------------------------------------------------------
void STBTextEditView::calcCursorSizes ()
{
if (cursorSizesValid ())
return;
auto platformFont = getFont ()->getPlatformFont ();
vstgui_assert (platformFont);
cursorHeight = platformFont->getAscent () + platformFont->getDescent ();
auto viewHeight = getViewSize ().getHeight ();
cursorOffset = (viewHeight / 2. - cursorHeight / 2.);
setCursorSizesValid (true);
}
//-----------------------------------------------------------------------------
void STBTextEditView::draw (CDrawContext* context)
{
fillCharWidthCache ();
calcCursorSizes ();
drawBack (context, nullptr);
drawPlatformText (context, getText ());
if (!isBlinkToggle () || editState.select_start != editState.select_end)
return;
// draw cursor
StbTexteditRow row{};
layout (&row, this, 0);
context->setFillColor (getFontColor ());
context->setDrawMode (kAntiAliasing);
CRect r = getViewSize ();
r.setHeight (cursorHeight);
r.offset (row.x0, cursorOffset);
r.setWidth (1);
for (auto i = 0; i < editState.cursor; ++i)
r.offset (charWidthCache[i], 0);
r.offset (-0.5, 0);
context->drawRect (r, kDrawFilled);
}
//-----------------------------------------------------------------------------
void STBTextEditView::drawBack (CDrawContext* context, CBitmap* newBack)
{
CTextLabel::drawBack (context, newBack);
auto selStart = editState.select_start;
auto selEnd = editState.select_end;
if (selStart > selEnd)
std::swap (selStart, selEnd);
if (selStart != selEnd)
{
StbTexteditRow row{};
layout (&row, this, 0);
// draw selection
CRect selection = getViewSize ();
selection.setHeight (cursorHeight);
selection.offset (row.x0, cursorOffset);
selection.setWidth (0);
auto index = 0;
for (; index < selStart; ++index)
selection.offset (charWidthCache[index], 0);
for (; index < selEnd; ++index)
selection.right += charWidthCache[index];
context->setFillColor (selectionColor);
context->drawRect (selection, kDrawFilled);
}
}
//-----------------------------------------------------------------------------
void STBTextEditView::onTextChange ()
{
if (notifyTextChange ())
return;
if (auto frame = getFrame ())
{
if (frame->inEventProcessing ())
{
setNotifyTextChange (true);
auto self = shared (this);
frame->doAfterEventProcessing ([self]() {
self->setNotifyTextChange (false);
self->callback->platformTextDidChange ();
});
}
}
}
//-----------------------------------------------------------------------------
int STBTextEditView::deleteChars (STBTextEditView* self, size_t pos, size_t num)
{
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
self->uString.erase (pos, num);
self->setText (StringConvert{}.to_bytes (reinterpret_cast<const STB_CharT*> (self->uString.data ()), reinterpret_cast<const STB_CharT*> (self->uString.data () + self->uString.size ())));
self->onTextChange ();
return true;
#else
auto str = self->text.getString ();
str.erase (pos, num);
self->setText (str.data ());
self->onTextChange ();
return true; // success
#endif
}
//-----------------------------------------------------------------------------
int STBTextEditView::insertChars (STBTextEditView* self,
size_t pos,
const STB_CharT* text,
size_t num)
{
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
self->uString.insert (pos, reinterpret_cast<const char16_t*> (text), num);
self->setText (StringConvert{}.to_bytes (reinterpret_cast<const STB_CharT*> (self->uString.data ()), reinterpret_cast<const STB_CharT*> (self->uString.data () + self->uString.size ())));
self->onTextChange ();
return true;
#else
auto str = self->text.getString ();
str.insert (pos, text, num);
self->setText (str.data ());
self->onTextChange ();
return true; // success
#endif
}
//-----------------------------------------------------------------------------
STB_CharT STBTextEditView::getChar (STBTextEditView* self, int pos)
{
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
return self->uString[pos];
#else
return self->getText ().getString ()[pos];
#endif
}
//-----------------------------------------------------------------------------
int STBTextEditView::getLength (STBTextEditView* self)
{
#if VSTGUI_STB_TEXTEDIT_USE_UNICODE
return static_cast<int> (self->uString.size ());
#else
return static_cast<int> (self->getText ().length ());
#endif
}
//-----------------------------------------------------------------------------
void STBTextEditView::layout (StbTexteditRow* row, STBTextEditView* self, int start_i)
{
vstgui_assert (start_i == 0);
self->fillCharWidthCache ();
auto textWidth = static_cast<float> (
std::accumulate (self->charWidthCache.begin (), self->charWidthCache.end (), 0.));
row->num_chars = static_cast<int> (self->getText ().length ());
row->baseline_y_delta = 1.25;
row->ymin = 0.f;
row->ymax = static_cast<float> (self->getFont ()->getSize ());
switch (self->getHoriAlign ())
{
case kLeftText:
{
row->x0 = static_cast<float> (self->getTextInset ().x);
row->x1 = row->x0 + textWidth;
break;
}
case kCenterText:
{
row->x0 =
static_cast<float> ((self->getViewSize ().getWidth () / 2.) - (textWidth / 2.));
row->x1 = row->x0 + textWidth;
break;
}
default:
{
vstgui_assert (false, "Not Implemented !");
break;
}
}
}
//-----------------------------------------------------------------------------
float STBTextEditView::getCharWidth (STBTextEditView* self, int n, int i)
{
self->fillCharWidthCache ();
return static_cast<float> (self->charWidthCache[i]);
}
//-----------------------------------------------------------------------------
} // VSTGUI
#ifdef __clang__
#pragma clang diagnostic pop
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif
@@ -0,0 +1,31 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformtextedit.h"
#include <memory>
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
class GenericTextEdit : public IPlatformTextEdit
{
public:
GenericTextEdit (IPlatformTextEditCallback* callback);
~GenericTextEdit () noexcept;
UTF8String getText () override;
bool setText (const UTF8String& text) override;
bool updateSize () override;
bool drawsPlaceholder () const override { return false; }
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//-----------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,36 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformgradient.h"
namespace VSTGUI {
//-----------------------------------------------------------------------------
class PlatformGradientBase : public IPlatformGradient
{
public:
void setColorStops (const GradientColorStopMap& colorStops) override
{
map = colorStops;
changed ();
}
void addColorStop (const GradientColorStop& colorStop) override
{
map.emplace (colorStop);
changed ();
}
const GradientColorStopMap& getColorStops () const override
{
return map;
}
virtual void changed () {}
private:
GradientColorStopMap map;
};
} // VSTGUI
@@ -0,0 +1,283 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformtaskexecutor.h"
#include "../../vstguifwd.h"
#include <atomic>
#include <condition_variable>
#include <functional>
#include <queue>
#include <mutex>
#include <thread>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Tasks {
namespace Detail {
//------------------------------------------------------------------------
struct ThreadPool
{
explicit ThreadPool (size_t numThreads) : workers (numThreads) {}
~ThreadPool () noexcept
{
if (started)
{
stopThreads ();
condition.notify_all ();
joinAllThreads ();
}
}
void enqueue (Task&& task) noexcept
{
std::unique_lock<std::mutex> lock (queueMutex);
vstgui_assert (!stop, "task is not executed, because the thread pool is already stopped");
if (stop)
return;
++numTasks;
taskQueue.emplace (std::move (task));
if (!started)
startThreads ();
lock.unlock ();
condition.notify_one ();
}
bool empty () const noexcept { return numTasks == 0u; }
private:
void startThreads () noexcept
{
started = true;
for (size_t i = 0; i < workers.size (); ++i)
{
workers[i] = std::thread ([this] () { workerLoop (); });
}
}
void stopThreads () noexcept
{
std::lock_guard<std::mutex> lock (queueMutex);
stop = true;
started = false;
}
void joinAllThreads () noexcept
{
for (auto& worker : workers)
{
worker.join ();
}
}
void workerLoop () noexcept
{
while (!stop)
{
Task task;
std::unique_lock<std::mutex> lock (queueMutex);
condition.wait (lock, [this] () { return stop || !taskQueue.empty (); });
if (!stop && !taskQueue.empty ())
{
task = std::move (taskQueue.front ());
taskQueue.pop ();
}
lock.unlock ();
if (task)
{
task ();
--numTasks;
}
}
}
std::vector<std::thread> workers;
std::queue<Task> taskQueue;
std::atomic<uint64_t> numTasks {0u};
std::atomic<bool> stop {false};
std::atomic<bool> started {false};
std::mutex queueMutex;
std::condition_variable condition;
};
//------------------------------------------------------------------------
struct SerialQueue
{
SerialQueue (ThreadPool& pool, uint64_t inIdentifier, const char* inName)
: threadPool (pool), identifier (inIdentifier), name (inName)
{
}
~SerialQueue () noexcept { vstgui_assert (empty (), "Serial Queue is destroyed non empty"); }
uint64_t getIdentifier () const noexcept { return identifier; }
void schedule (Task&& task) noexcept
{
std::lock_guard<std::mutex> lock (queueMutex);
taskQueue.push (std::move (task));
if (!scheduled)
{
scheduled = true;
threadPool.enqueue ([this] () { runAndScheduleNextTask (); });
}
}
bool empty () const noexcept
{
std::lock_guard<std::mutex> lock (queueMutex);
return taskQueue.empty ();
}
private:
void runAndScheduleNextTask () noexcept
{
Task task;
{
std::lock_guard<std::mutex> lock (queueMutex);
task = std::move (taskQueue.front ());
taskQueue.pop ();
}
task ();
std::lock_guard<std::mutex> lock (queueMutex);
if (taskQueue.empty ())
{
scheduled = false;
}
else
{
threadPool.enqueue ([this] () { runAndScheduleNextTask (); });
}
}
ThreadPool& threadPool;
uint64_t identifier;
std::queue<Task> taskQueue;
std::string name;
std::atomic_bool scheduled {false};
mutable std::mutex queueMutex;
};
//------------------------------------------------------------------------
} // Detail
//------------------------------------------------------------------------
struct ThreadPoolTaskExecutor : IPlatformTaskExecutor
{
using SerialQueueVector = std::vector<std::unique_ptr<Detail::SerialQueue>>;
ThreadPoolTaskExecutor (PlatformTaskExecutorPtr&& inPlatformTaskExecutor)
: backgroundQueue ({inPlatformTaskExecutor->getMainQueue ().identifier + 1})
, platformTaskExecutor (std::move (inPlatformTaskExecutor))
{
queueIdentifierCounter = backgroundQueue.identifier;
}
~ThreadPoolTaskExecutor () noexcept override
{
std::lock_guard<std::mutex> lock (serialQueueMutex);
serialQueues.clear ();
}
const Queue& getMainQueue () const override { return platformTaskExecutor->getMainQueue (); }
const Queue& getBackgroundQueue () const override { return backgroundQueue; }
Queue makeSerialQueue (const char* name) const override
{
std::lock_guard<std::mutex> lock (serialQueueMutex);
serialQueues.emplace_back (
std::make_unique<Detail::SerialQueue> (threadPool, ++queueIdentifierCounter, name));
return {queueIdentifierCounter};
}
void releaseSerialQueue (const Queue& queue) const override
{
std::lock_guard<std::mutex> lock (serialQueueMutex);
auto it = findQueue (queue.identifier);
if (it != serialQueues.end ())
{
waitAllTasksExecuted (it);
serialQueues.erase (it);
}
}
void schedule (const Queue& queue, Task&& task) const override
{
if (queue == getMainQueue ())
{
platformTaskExecutor->schedule (queue, std::move (task));
}
else if (queue == backgroundQueue)
{
threadPool.enqueue (std::move (task));
}
else
{
std::lock_guard<std::mutex> lock (serialQueueMutex);
auto it = findQueue (queue.identifier);
if (it != serialQueues.end ())
(*it)->schedule (std::move (task));
}
}
void waitAllTasksExecuted (const Queue& queue) const override
{
if (queue == getMainQueue ())
{
platformTaskExecutor->waitAllTasksExecuted (queue);
}
else if (queue == backgroundQueue)
{
while (!threadPool.empty ())
std::this_thread::sleep_for (std::chrono::milliseconds (1));
}
else
{
std::lock_guard<std::mutex> lock (serialQueueMutex);
auto it = findQueue (queue.identifier);
if (it != serialQueues.end ())
waitAllTasksExecuted (it);
}
}
void waitAllTasksExecuted () const override
{
{
std::lock_guard<std::mutex> lock (serialQueueMutex);
for (auto it = serialQueues.begin (); it != serialQueues.end (); ++it)
waitAllTasksExecuted (it);
}
waitAllTasksExecuted (backgroundQueue);
waitAllTasksExecuted (getMainQueue ());
platformTaskExecutor->waitAllTasksExecuted ();
}
private:
void waitAllTasksExecuted (SerialQueueVector::const_iterator it) const
{
while ((*it)->empty () == false)
std::this_thread::sleep_for (std::chrono::milliseconds (1));
}
SerialQueueVector::const_iterator findQueue (uint64_t identifier) const
{
return std::find_if (serialQueues.begin (), serialQueues.end (),
[&] (const auto& el) { return el->getIdentifier (); });
}
Queue backgroundQueue;
PlatformTaskExecutorPtr platformTaskExecutor;
mutable Detail::ThreadPool threadPool {std::thread::hardware_concurrency ()};
mutable uint64_t queueIdentifierCounter {};
mutable SerialQueueVector serialQueues;
mutable std::mutex serialQueueMutex;
};
//------------------------------------------------------------------------
} // Tasks
} // VSTGUI
@@ -0,0 +1,46 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
/// @cond ignore
#include "../vstguifwd.h"
#include <vector>
namespace VSTGUI {
class IPlatformBitmapPixelAccess;
//-----------------------------------------------------------------------------
class IPlatformBitmap : public AtomicReferenceCounted
{
public:
virtual const CPoint& getSize () const = 0;
virtual SharedPointer<IPlatformBitmapPixelAccess> lockPixels (bool alphaPremultiplied) = 0;
virtual void setScaleFactor (double factor) = 0;
virtual double getScaleFactor () const = 0;
};
//------------------------------------------------------------------------------------
class IPlatformBitmapPixelAccess : public AtomicReferenceCounted
{
public:
enum PixelFormat
{
kARGB,
kRGBA,
kABGR,
kBGRA
};
virtual uint8_t* getAddress () const = 0;
virtual uint32_t getBytesPerRow () const = 0;
virtual PixelFormat getPixelFormat () const = 0;
};
} // VSTGUI
/// @endcond
@@ -0,0 +1,83 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../cstring.h"
#include <functional>
#include <vector>
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
struct PlatformFileExtension
{
UTF8String description;
UTF8String extension;
UTF8String mimeType;
UTF8String uti;
int32_t macType {0};
};
static PlatformFileExtension PlatformAllFilesExtension = {"All Files", "", "", "", 0};
static PlatformFileExtension PlatformNoFileExtension = {};
//-----------------------------------------------------------------------------
enum class PlatformFileSelectorStyle : uint32_t
{
SelectFile,
SelectDirectory,
SelectSaveFile,
};
//-----------------------------------------------------------------------------
enum class PlatformFileSelectorFlags : uint32_t
{
MultiFileSelection = 1 << 0,
RunModal = 1 << 1,
};
//-----------------------------------------------------------------------------
struct PlatformFileSelectorConfig
{
using CallbackFunc = std::function<void (std::vector<UTF8String>&&)>;
using FileExtensionList = std::vector<PlatformFileExtension>;
UTF8String title;
UTF8String initialPath;
UTF8String defaultSaveName;
FileExtensionList extensions;
PlatformFileExtension defaultExtension;
uint32_t flags {0};
CallbackFunc doneCallback;
};
//-----------------------------------------------------------------------------
class IPlatformFileSelector
{
public:
virtual bool run (const PlatformFileSelectorConfig& config) = 0;
virtual bool cancel () = 0;
virtual ~IPlatformFileSelector () noexcept = default;
};
//-----------------------------------------------------------------------------
inline bool operator== (const PlatformFileExtension& e1, const PlatformFileExtension& e2)
{
return e1.macType == e2.macType && e1.uti == e2.uti && e1.mimeType == e2.mimeType &&
e1.extension == e2.extension && e1.description == e2.description;
}
//-----------------------------------------------------------------------------
inline bool operator!= (const PlatformFileExtension& e1, const PlatformFileExtension& e2)
{
return e1.macType != e2.macType || e1.uti != e2.uti || e1.mimeType != e2.mimeType ||
e1.extension != e2.extension || e1.description != e2.description;
}
//-----------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,50 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguifwd.h"
#include <list>
namespace VSTGUI {
//-----------------------------------------------------------------------------
// IFontPainter Declaration
//! @brief font paint interface
//-----------------------------------------------------------------------------
class IFontPainter
{
public:
virtual ~IFontPainter () noexcept = default;
virtual void drawString (const PlatformGraphicsDeviceContextPtr& context,
IPlatformString* string, const CPoint& p, const CColor& color,
bool antialias = true) const = 0;
virtual CCoord getStringWidth (const PlatformGraphicsDeviceContextPtr& context,
IPlatformString* string, bool antialias = true) const = 0;
};
//-----------------------------------------------------------------------------
// IPlatformFont declaration
//! @brief platform font class
///
/// Encapsulation of a platform font.
//-----------------------------------------------------------------------------
class IPlatformFont : public AtomicReferenceCounted
{
public:
/** returns the ascent line offset of the baseline of this font. If not supported returns -1 */
virtual double getAscent () const = 0;
/** returns the descent line offset of the baseline of this font. If not supported returns -1 */
virtual double getDescent () const = 0;
/** returns the space between lines for this font. If not supported returns -1 */
virtual double getLeading () const = 0;
/** returns the height of the highest capital letter for this font. If not supported returns -1
*/
virtual double getCapHeight () const = 0;
virtual const IFontPainter* getPainter () const = 0;
};
} // VSTGUI
@@ -0,0 +1,119 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
/// @cond ignore
#include "../vstguifwd.h"
#include "../dragging.h"
#include "../optional.h"
#include "../cstring.h"
#include "iplatformframecallback.h"
namespace VSTGUI {
struct GenericOptionMenuTheme;
//-----------------------------------------------------------------------------
class IPlatformFrame : public AtomicReferenceCounted
{
public:
/** get the top left position in global coordinates */
virtual bool getGlobalPosition (CPoint& pos) const = 0;
/** set size of platform representation relative to parent */
virtual bool setSize (const CRect& newSize) = 0;
/** get size of platform representation relative to parent */
virtual bool getSize (CRect& size) const = 0;
/** get current mouse position out of event stream */
virtual bool getCurrentMousePosition (CPoint& mousePosition) const = 0;
/** get current mouse buttons out of event stream */
virtual bool getCurrentMouseButtons (CButtonState& buttons) const = 0;
/** get current hardware modifier key state */
virtual bool getCurrentModifiers (Modifiers& modifiers) const = 0;
/** set mouse cursor shape */
virtual bool setMouseCursor (CCursorType type) = 0;
/** invalidates rect in platform representation*/
virtual bool invalidRect (const CRect& rect) = 0;
/** blit scroll the src rect by distance, return false if not supported */
virtual bool scrollRect (const CRect& src, const CPoint& distance) = 0;
/** show tooltip */
virtual bool showTooltip (const CRect& rect, const char* utf8Text) = 0;
/** hide tooltip */
virtual bool hideTooltip () = 0;
/** TODO: remove this call later when everything is done */
virtual void* getPlatformRepresentation () const = 0;
/** create a native text edit control */
virtual SharedPointer<IPlatformTextEdit>
createPlatformTextEdit (IPlatformTextEditCallback* textEdit) = 0;
/** create a native popup menu */
virtual SharedPointer<IPlatformOptionMenu> createPlatformOptionMenu () = 0;
#if VSTGUI_OPENGL_SUPPORT
/** create a native opengl sub view */
virtual SharedPointer<IPlatformOpenGLView> createPlatformOpenGLView () = 0;
#endif // VSTGUI_OPENGL_SUPPORT
/** create a native view layer, may return 0 if not supported */
virtual SharedPointer<IPlatformViewLayer> createPlatformViewLayer (
IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer = nullptr) = 0;
#if VSTGUI_ENABLE_DEPRECATED_METHODS
/** start a drag operation */
virtual DragResult doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) = 0;
#endif
/** start a drag operation
*
* optional callback will be remembered until the drag is droped or canceled
*/
virtual bool doDrag (const DragDescription& dragDescription,
const SharedPointer<IDragCallback>& callback) = 0;
/** */
virtual PlatformType getPlatformType () const = 0;
/** called from IPlatformFrameCallback when it's closed */
virtual void onFrameClosed () = 0;
/** when called from a key down/up event converts the event to the actual text. */
virtual Optional<UTF8String> convertCurrentKeyEventToText () = 0;
/** setup to use (or not) the generic option menu and optionally set the theme to use */
virtual bool setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme = nullptr) = 0;
//-----------------------------------------------------------------------------
protected:
explicit IPlatformFrame (IPlatformFrameCallback* frame) : frame (frame) {}
IPlatformFrameCallback* frame;
};
//-----------------------------------------------------------------------------
/* Extension to support Mac TouchBar */
//-----------------------------------------------------------------------------
class ITouchBarCreator : public AtomicReferenceCounted
{
public:
/** must return an instance of NSTouchBar or nullptr. */
virtual void* createTouchBar () = 0;
};
//-----------------------------------------------------------------------------
class IPlatformFrameTouchBarExtension /* Extents IPlatformFrame */
{
public:
virtual ~IPlatformFrameTouchBarExtension () noexcept = default;
/** set the touchbar creator. */
virtual void setTouchBarCreator (const SharedPointer<ITouchBarCreator>& creator) = 0;
/** forces the touchbar to be recreated. */
virtual void recreateTouchBar () = 0;
};
} // VSTGUI
/// @endcond
@@ -0,0 +1,67 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
/// @cond ignore
#include "../vstguifwd.h"
struct VstKeyCode;
namespace VSTGUI {
//-----------------------------------------------------------------------------
enum class PlatformType : int32_t {
kHWND, // Windows HWND
kNSView, // macOS NSView
kUIView, // iOS UIView
kHWNDTopLevel, // Windows HWDN Top Level (non child)
kX11EmbedWindowID, // X11 XID
kWaylandSurfaceID, // Wayland Surface ID
kGdkWindow, // GdkWindow
kDefaultNative = -1
};
//-----------------------------------------------------------------------------
// Callback interface from IPlatformFrame implementations
//-----------------------------------------------------------------------------
class IPlatformFrameCallback
{
public:
virtual ~IPlatformFrameCallback () = default;
virtual void platformDrawRects (const PlatformGraphicsDeviceContextPtr& context,
double scaleFactor, const std::vector<CRect>& rects) = 0;
virtual void platformOnEvent (Event& event) = 0;
virtual DragOperation platformOnDragEnter (DragEventData data) = 0;
virtual DragOperation platformOnDragMove (DragEventData data) = 0;
virtual void platformOnDragLeave (DragEventData data) = 0;
virtual bool platformOnDrop (DragEventData data) = 0;
virtual void platformOnActivate (bool state) = 0;
virtual void platformOnWindowActivate (bool state) = 0;
virtual void platformScaleFactorChanged (double newScaleFactor) = 0;
#if VSTGUI_TOUCH_EVENT_HANDLING
virtual void platformOnTouchEvent (ITouchEvent& event) = 0;
#endif
//------------------------------------------------------------------------------------
};
//------------------------------------------------------------------------------------
class IPlatformFrameConfig
{
public:
virtual ~IPlatformFrameConfig () noexcept = default;
};
//------------------------------------------------------------------------------------
} // VSTGUI
/// @endcond
@@ -0,0 +1,22 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../ccolor.h"
namespace VSTGUI {
//-----------------------------------------------------------------------------
class IPlatformGradient
{
public:
virtual void setColorStops (const GradientColorStopMap& colorStops) = 0;
virtual void addColorStop (const GradientColorStop& colorStop) = 0;
virtual const GradientColorStopMap& getColorStops () const = 0;
virtual ~IPlatformGradient () noexcept = default;
};
} // VSTGUI
@@ -0,0 +1,151 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguifwd.h"
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
enum class PlatformGraphicsDrawStyle : uint32_t
{
Stroked,
Filled,
FilledAndStroked
};
//------------------------------------------------------------------------
enum class PlatformGraphicsPathDrawMode : uint32_t
{
Filled,
FilledEvenOdd,
Stroked
};
//------------------------------------------------------------------------
using TransformMatrix = CGraphicsTransform;
//------------------------------------------------------------------------
struct ScreenInfo
{
using Identifier = uint32_t;
/*
Identifier identifier;
uint32_t width;
uint32_t height;
*/
};
static constexpr const ScreenInfo::Identifier DefaultScreenIdentifier = 0u;
//------------------------------------------------------------------------
class IPlatformGraphicsDeviceFactory
{
public:
virtual ~IPlatformGraphicsDeviceFactory () noexcept = default;
virtual PlatformGraphicsDevicePtr getDeviceForScreen (ScreenInfo::Identifier screen) const = 0;
};
//------------------------------------------------------------------------
class IPlatformGraphicsDevice
{
public:
virtual ~IPlatformGraphicsDevice () noexcept = default;
virtual PlatformGraphicsDeviceContextPtr
createBitmapContext (const PlatformBitmapPtr& bitmap) const = 0;
};
//------------------------------------------------------------------------
class IPlatformGraphicsDeviceContext
{
public:
virtual ~IPlatformGraphicsDeviceContext () noexcept = default;
virtual const IPlatformGraphicsDevice& getDevice () const = 0;
virtual PlatformGraphicsPathFactoryPtr getGraphicsPathFactory () const = 0;
virtual bool beginDraw () const = 0;
virtual bool endDraw () const = 0;
// draw commands
virtual bool drawLine (LinePair line) const = 0;
virtual bool drawLines (const LineList& lines) const = 0;
virtual bool drawPolygon (const PointList& polygonPointList,
PlatformGraphicsDrawStyle drawStyle) const = 0;
virtual bool drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const = 0;
virtual bool drawArc (CRect rect, double startAngle1, double endAngle2,
PlatformGraphicsDrawStyle drawStyle) const = 0;
virtual bool drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const = 0;
virtual bool drawPoint (CPoint point, CColor color) const = 0;
virtual bool drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, double alpha,
BitmapInterpolationQuality quality) const = 0;
virtual bool clearRect (CRect rect) const = 0;
virtual bool drawGraphicsPath (IPlatformGraphicsPath& path, PlatformGraphicsPathDrawMode mode,
TransformMatrix* transformation) const = 0;
virtual bool fillLinearGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient,
CPoint startPoint, CPoint endPoint, bool evenOdd,
TransformMatrix* transformation) const = 0;
virtual bool fillRadialGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient,
CPoint center, CCoord radius, CPoint originOffset,
bool evenOdd, TransformMatrix* transformation) const = 0;
// state
virtual void saveGlobalState () const = 0;
virtual void restoreGlobalState () const = 0;
virtual void setLineStyle (const CLineStyle& style) const = 0;
virtual void setLineWidth (CCoord width) const = 0;
virtual void setDrawMode (CDrawMode mode) const = 0;
virtual void setClipRect (CRect clip) const = 0;
virtual void setFillColor (CColor color) const = 0;
virtual void setFrameColor (CColor color) const = 0;
virtual void setGlobalAlpha (double newAlpha) const = 0;
virtual void setTransformMatrix (const TransformMatrix& tm) const = 0;
// extension
virtual const IPlatformGraphicsDeviceContextBitmapExt* asBitmapExt () const = 0;
};
//------------------------------------------------------------------------
class IPlatformGraphicsDeviceContextBitmapExt
{
public:
virtual ~IPlatformGraphicsDeviceContextBitmapExt () noexcept = default;
virtual bool drawBitmapNinePartTiled (IPlatformBitmap& bitmap, CRect dest,
const CNinePartTiledDescription& desc, double alpha,
BitmapInterpolationQuality quality) const = 0;
virtual bool fillRectWithBitmap (IPlatformBitmap& bitmap, CRect srcRect, CRect dstRect,
double alpha, BitmapInterpolationQuality quality) const = 0;
};
//------------------------------------------------------------------------
enum class LineCap : uint32_t
{
Butt = 0,
Round,
Square
};
//------------------------------------------------------------------------
enum class LineJoin : uint32_t
{
Miter = 0,
Round,
Bevel
};
//------------------------------------------------------------------------
class IPlatformGraphicsDeviceContextGradientExt
{
public:
virtual ~IPlatformGraphicsDeviceContextGradientExt () noexcept = default;
virtual bool drawLinearGradientLine (const PointList& line, const IPlatformGradient& gradient,
CCoord lineWidth, LineCap lineCap,
LineJoin lineJoin) const = 0;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,64 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../ccolor.h"
#include "../cgraphicstransform.h"
#include "../cpoint.h"
#include "../crect.h"
namespace VSTGUI {
//------------------------------------------------------------------------
enum class PlatformGraphicsPathFillMode : int32_t
{
Winding,
Alternate,
Ignored
};
//------------------------------------------------------------------------
class IPlatformGraphicsPathFactory
{
public:
virtual PlatformGraphicsPathPtr createPath (
PlatformGraphicsPathFillMode fillMode = PlatformGraphicsPathFillMode::Winding) = 0;
virtual PlatformGraphicsPathPtr createTextPath (const PlatformFontPtr& font,
UTF8StringPtr text) = 0;
virtual ~IPlatformGraphicsPathFactory () noexcept = default;
};
//-----------------------------------------------------------------------------
class IPlatformGraphicsPath
{
public:
/** add an arc to the path. Begins a new subpath if no elements were added before. */
virtual void addArc (const CRect& rect, double startAngle, double endAngle, bool clockwise) = 0;
/** add an ellipse to the path. Begins a new subpath if no elements were added before. */
virtual void addEllipse (const CRect& rect) = 0;
/** add a rectangle to the path. Begins a new subpath if no elements were added before. */
virtual void addRect (const CRect& rect) = 0;
/** add a line to the path. A subpath must begin before */
virtual void addLine (const CPoint& to) = 0;
/** add a bezier curve to the path. A subpath must begin before */
virtual void addBezierCurve (const CPoint& control1, const CPoint& control2,
const CPoint& end) = 0;
/** begin a new subpath. */
virtual void beginSubpath (const CPoint& start) = 0;
/** close a subpath. A straight line will be added from the current point to the start point. */
virtual void closeSubpath () = 0;
virtual void finishBuilding () = 0;
virtual bool hitTest (const CPoint& p, bool evenOddFilled = false,
CGraphicsTransform* transform = nullptr) const = 0;
virtual CRect getBoundingBox () const = 0;
virtual PlatformGraphicsPathFillMode getFillMode () const = 0;
virtual ~IPlatformGraphicsPath () noexcept = default;
};
} // VSTGUI
@@ -0,0 +1,71 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguifwd.h"
/// @cond ignore
#if VSTGUI_OPENGL_SUPPORT
namespace VSTGUI {
class IPlatformFrame;
//-----------------------------------------------------------------------------
struct PixelFormat
{
// TODO: add more if we need more...
enum {
kDoubleBuffered = 1 << 0,
kMultiSample = 1 << 2,
kModernOpenGL = 1 << 3 // Mac only. Indicates to use the NSOpenGLProfileVersion3_2Core.
};
uint32_t depthBufferSize {32};
uint32_t stencilBufferSize {0};
/** only used when kMultiSample is set */
uint32_t samples {0};
uint32_t flags {kDoubleBuffered};
PixelFormat () = default;
PixelFormat (const PixelFormat&) = default;
PixelFormat& operator= (const PixelFormat&) = default;
};
//-----------------------------------------------------------------------------
class IOpenGLView
{
public:
virtual void drawOpenGL (const CRect& updateRect) = 0;
virtual void reshape () = 0;
};
//-----------------------------------------------------------------------------
class IPlatformOpenGLView : public AtomicReferenceCounted
{
public:
virtual bool init (IOpenGLView* view, PixelFormat* pixelFormat = nullptr) = 0;
virtual void remove () = 0;
virtual void invalidRect (const CRect& rect) = 0;
/** visibleSize is cframe relative */
virtual void viewSizeChanged (const CRect& visibleSize) = 0;
/** make OpenGL context active */
virtual bool makeContextCurrent () = 0;
/** lock changes to context */
virtual bool lockContext () = 0;
/** unlock changes to context */
virtual bool unlockContext () = 0;
/** swap buffers and clear active OpenGL context */
virtual void swapBuffers () = 0;
};
} // VSTGUI
#endif // VSTGUI_OPENGL_SUPPORT
/// @endcond
@@ -0,0 +1,30 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
/// @cond ignore
#include "../vstguifwd.h"
namespace VSTGUI {
//-----------------------------------------------------------------------------
struct PlatformOptionMenuResult
{
COptionMenu* menu;
int32_t index;
};
//-----------------------------------------------------------------------------
class IPlatformOptionMenu : public AtomicReferenceCounted
{
public:
using Callback = std::function<void (COptionMenu* optionMenu, PlatformOptionMenuResult result)>;
virtual void popup (COptionMenu* optionMenu, const Callback& callback) = 0;
};
} // VSTGUI
/// @endcond
@@ -0,0 +1,26 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguifwd.h"
#include <functional>
#include <memory>
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
class IPlatformResourceInputStream
{
public:
virtual ~IPlatformResourceInputStream () noexcept = default;
virtual uint32_t readRaw (void* buffer, uint32_t size) = 0;
virtual int64_t seek (int64_t pos, SeekMode mode) = 0;
virtual int64_t tell () = 0;
};
//-----------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,22 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguibase.h"
/// @cond ignore
namespace VSTGUI {
//-----------------------------------------------------------------------------
class IPlatformString : public AtomicReferenceCounted
{
public:
virtual void setUTF8String (UTF8StringPtr utf8String) = 0;
};
} // VSTGUI
/// @endcond
@@ -0,0 +1,55 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include <functional>
#include <memory>
#include <limits>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Tasks {
//------------------------------------------------------------------------
struct Queue final
{
const uint64_t identifier;
};
static constexpr Queue InvalidQueue = Queue {std::numeric_limits<uint64_t>::max ()};
//------------------------------------------------------------------------
inline bool operator== (const Queue& q1, const Queue& q2) noexcept
{
return q1.identifier == q2.identifier;
}
//------------------------------------------------------------------------
inline bool operator!= (const Queue& q1, const Queue& q2) noexcept
{
return q1.identifier != q2.identifier;
}
using Task = std::function<void ()>;
//------------------------------------------------------------------------
} // Tasks
//------------------------------------------------------------------------
class IPlatformTaskExecutor
{
public:
virtual ~IPlatformTaskExecutor () noexcept = default;
virtual const Tasks::Queue& getMainQueue () const = 0;
virtual const Tasks::Queue& getBackgroundQueue () const = 0;
virtual Tasks::Queue makeSerialQueue (const char* name) const = 0;
virtual void releaseSerialQueue (const Tasks::Queue& queue) const = 0;
virtual void schedule (const Tasks::Queue& queue, Tasks::Task&& task) const = 0;
virtual void waitAllTasksExecuted (const Tasks::Queue& queue) const = 0;
virtual void waitAllTasksExecuted () const = 0;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,56 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
/// @cond ignore
#include "../cfont.h"
#include "../ccolor.h"
#include "../crect.h"
#include "../cdrawdefs.h"
struct VstKeyCode;
namespace VSTGUI {
//-----------------------------------------------------------------------------
class IPlatformTextEditCallback
{
public:
virtual CColor platformGetBackColor () const = 0;
virtual CColor platformGetFontColor () const = 0;
virtual CFontRef platformGetFont () const = 0;
virtual CHoriTxtAlign platformGetHoriTxtAlign () const = 0;
virtual const UTF8String& platformGetText () const = 0;
virtual const UTF8String& platformGetPlaceholderText () const = 0;
virtual CRect platformGetSize () const = 0;
virtual CRect platformGetVisibleSize () const = 0;
virtual CPoint platformGetTextInset () const = 0;
virtual void platformLooseFocus (bool returnPressed) = 0;
virtual void platformOnKeyboardEvent (KeyboardEvent& event) = 0;
virtual void platformTextDidChange () = 0;
virtual bool platformIsSecureTextEdit () = 0;
//------------------------------------------------------------------------------------
};
//-----------------------------------------------------------------------------
class IPlatformTextEdit : public AtomicReferenceCounted
{
public:
virtual UTF8String getText () = 0;
virtual bool setText (const UTF8String& text) = 0;
virtual bool updateSize () = 0;
virtual bool drawsPlaceholder () const = 0;
//-----------------------------------------------------------------------------
protected:
explicit IPlatformTextEdit (IPlatformTextEditCallback* textEdit) : textEdit (textEdit) {}
IPlatformTextEditCallback* textEdit;
};
} // VSTGUI
/// @endcond
@@ -0,0 +1,61 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguifwd.h"
#include <functional>
#include <string>
namespace VSTGUI {
//------------------------------------------------------------------------
using TextInputClientCancelCallback = std::function<void ()>;
//------------------------------------------------------------------------
struct ICocoaTextInputClient
{
struct TextRange
{
size_t position;
size_t length;
};
virtual void insertText (const std::u32string& string, TextRange range) = 0;
virtual void setMarkedText (const std::u32string& string, TextRange selectedRange,
TextRange replacementRange) = 0;
virtual bool hasMarkedText () = 0;
virtual void unmarkText () = 0;
virtual TextRange getMarkedRange () = 0;
virtual TextRange getSelectedRange () = 0;
virtual CRect firstRectForCharacterRange (TextRange range, TextRange& actualRange) = 0;
virtual std::u32string substringForRange (TextRange range, TextRange& actualRange) = 0;
virtual size_t characterIndexForPoint (CPoint pos) = 0;
virtual void setCancelCallback (const TextInputClientCancelCallback& callback) = 0;
virtual ~ICocoaTextInputClient () noexcept = default;
};
//------------------------------------------------------------------------
struct IIMETextInputClient
{
struct CharPosition
{
uint32_t characterPosition;
CPoint position;
double lineHeight;
CRect documentRect;
};
virtual bool ime_queryCharacterPosition (CharPosition& cp) = 0;
virtual void ime_setMarkedText (const std::u32string& string) = 0;
virtual void ime_insertText (const std::u32string& string) = 0;
virtual void ime_unmarkText () = 0;
virtual ~IIMETextInputClient () noexcept = default;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,26 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguibase.h"
namespace VSTGUI {
//-----------------------------------------------------------------------------
class IPlatformTimerCallback
{
public:
virtual void fire () = 0;
};
//-----------------------------------------------------------------------------
class IPlatformTimer : public AtomicReferenceCounted
{
public:
virtual bool start (uint32_t fireTime) = 0;
virtual bool stop () = 0;
};
} // VSTGUI
@@ -0,0 +1,35 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../vstguifwd.h"
namespace VSTGUI {
//-----------------------------------------------------------------------------
class IPlatformViewLayerDelegate
{
public:
virtual ~IPlatformViewLayerDelegate () noexcept = default;
/** rects are in client coordinates (top-left is 0, 0) */
virtual void drawViewLayerRects (const PlatformGraphicsDeviceContextPtr& context,
double scaleFactor, const std::vector<CRect>& rects) = 0;
};
//-----------------------------------------------------------------------------
class IPlatformViewLayer : public AtomicReferenceCounted
{
public:
/** size must be zero based */
virtual void invalidRect (const CRect& size) = 0;
/** size is relative to platformParent */
virtual void setSize (const CRect& size) = 0;
virtual void setZIndex (uint32_t zIndex) = 0;
virtual void setAlpha (float alpha) = 0;
virtual void onScaleFactorChanged (double newScaleFactor) = 0;
};
} // VSTGUI
@@ -0,0 +1,285 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../../cpoint.h"
#include "../../cresourcedescription.h"
#include "linuxfactory.h"
#include "cairobitmap.h"
#include <memory>
#include <vector>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
namespace CairoBitmapPrivate {
//-----------------------------------------------------------------------------
struct PNGMemoryReader
{
PNGMemoryReader (const uint8_t* ptr, size_t size) : ptr (ptr), size (size) {}
cairo_surface_t* create () { return cairo_image_surface_create_from_png_stream (read, this); }
private:
static cairo_status_t read (void* closure, unsigned char* data, unsigned int length)
{
auto self = reinterpret_cast<PNGMemoryReader*> (closure);
auto numBytes = std::min<size_t> (length, self->size);
if (numBytes)
{
memcpy (data, self->ptr, numBytes);
self->ptr += numBytes;
self->size -= numBytes;
return CAIRO_STATUS_SUCCESS;
}
return CAIRO_STATUS_READ_ERROR;
}
const uint8_t* ptr;
size_t size;
};
//-----------------------------------------------------------------------------
struct PNGMemoryWriter
{
using Buffer = PNGBitmapBuffer;
Buffer create (cairo_surface_t* image)
{
Buffer buffer;
cairo_surface_write_to_png_stream (image, write, &buffer);
return buffer;
}
private:
static cairo_status_t write (void* closure, const unsigned char* data, unsigned int length)
{
auto buffer = reinterpret_cast<Buffer*> (closure);
if (!buffer)
return CAIRO_STATUS_WRITE_ERROR;
buffer->reserve (buffer->size () + length);
std::copy_n (data, length, std::back_inserter (*buffer));
return CAIRO_STATUS_SUCCESS;
}
};
//-----------------------------------------------------------------------------
static SurfaceHandle createImageFromPath (const char* path)
{
if (auto surface = cairo_image_surface_create_from_png (path))
{
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
{
cairo_surface_destroy (surface);
return {};
}
if (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32)
return SurfaceHandle {surface};
// vstgui always works with 32 bit images
auto x = cairo_image_surface_get_width (surface);
auto y = cairo_image_surface_get_height (surface);
auto surface32 = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, x, y);
vstgui_assert (cairo_surface_status (surface32) == CAIRO_STATUS_SUCCESS);
auto context = cairo_create (surface32);
vstgui_assert (cairo_status (context) == CAIRO_STATUS_SUCCESS);
cairo_set_source_surface (context, surface, 0, 0);
vstgui_assert (cairo_status (context) == CAIRO_STATUS_SUCCESS);
cairo_paint (context);
vstgui_assert (cairo_status (context) == CAIRO_STATUS_SUCCESS);
cairo_surface_flush (surface32);
vstgui_assert (cairo_status (context) == CAIRO_STATUS_SUCCESS);
cairo_destroy (context);
cairo_surface_destroy (surface);
return SurfaceHandle {surface32};
}
return {};
}
//-----------------------------------------------------------------------------
class PixelAccess : public IPlatformBitmapPixelAccess
{
public:
~PixelAccess () override;
bool init (Bitmap* bitmap, const SurfaceHandle& surface);
private:
uint8_t* address {nullptr};
uint32_t bytesPerRow {0};
uint8_t* getAddress () const override { return address; }
uint32_t getBytesPerRow () const override { return bytesPerRow; }
PixelFormat getPixelFormat () const override
{
#if __LITTLE_ENDIAN
return kBGRA;
#else
return kARGB;
#endif
}
SharedPointer<Bitmap> bitmap;
SurfaceHandle surface;
};
//-----------------------------------------------------------------------------
} // CairoBitmapPrivate
//-----------------------------------------------------------------------------
SharedPointer<Bitmap> Bitmap::create (UTF8StringPtr absolutePath)
{
if (auto surface = Cairo::CairoBitmapPrivate::createImageFromPath (absolutePath))
{
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
{
cairo_surface_destroy (surface);
return nullptr;
}
return makeOwned<Bitmap> (surface);
}
return nullptr;
}
//-----------------------------------------------------------------------------
SharedPointer<Bitmap> Bitmap::create (const void* ptr, uint32_t memSize)
{
Cairo::CairoBitmapPrivate::PNGMemoryReader reader (reinterpret_cast<const uint8_t*> (ptr),
memSize);
if (auto surface = reader.create ())
{
return makeOwned<Bitmap> (Cairo::SurfaceHandle {surface});
}
return nullptr;
}
//-----------------------------------------------------------------------------
Bitmap::Bitmap (const CPoint& _size)
{
size = _size;
surface = SurfaceHandle (cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size.x, size.y));
}
//-----------------------------------------------------------------------------
Bitmap::Bitmap () = default;
//-----------------------------------------------------------------------------
Bitmap::Bitmap (const SurfaceHandle& surface) : surface (surface)
{
size.x = cairo_image_surface_get_width (surface);
size.y = cairo_image_surface_get_height (surface);
}
//-----------------------------------------------------------------------------
Bitmap::~Bitmap () {}
//-----------------------------------------------------------------------------
bool Bitmap::load (const CResourceDescription& desc)
{
auto linuxFactory = getPlatformFactory ().asLinuxFactory ();
if (!linuxFactory)
return false;
auto path = linuxFactory->getResourcePath ();
if (!path.empty ())
{
if (desc.type == CResourceDescription::kIntegerType)
{
char filename[PATH_MAX];
snprintf (filename, PATH_MAX, "bmp%05d.png", (int32_t)desc.u.id);
path += filename;
}
else
{
path += desc.u.name;
}
if (auto s = CairoBitmapPrivate::createImageFromPath (path.data ()))
{
if (cairo_surface_status (s) != CAIRO_STATUS_SUCCESS)
{
cairo_surface_destroy (s);
return false;
}
surface = s;
size.x = cairo_image_surface_get_width (surface);
size.y = cairo_image_surface_get_height (surface);
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
const CPoint& Bitmap::getSize () const
{
return size;
}
//-----------------------------------------------------------------------------
SharedPointer<IPlatformBitmapPixelAccess> Bitmap::lockPixels (bool alphaPremultiplied)
{
if (locked)
return nullptr;
#warning TODO: alphaPremultiplied is currently ignored, always treated as true
locked = true;
auto pixelAccess = owned (new CairoBitmapPrivate::PixelAccess ());
if (pixelAccess->init (this, surface))
return pixelAccess;
return nullptr;
}
//-----------------------------------------------------------------------------
void Bitmap::setScaleFactor (double factor)
{
scaleFactor = factor;
}
//-----------------------------------------------------------------------------
double Bitmap::getScaleFactor () const
{
return scaleFactor;
}
//-----------------------------------------------------------------------------
PNGBitmapBuffer Bitmap::createMemoryPNGRepresentation () const
{
Cairo::CairoBitmapPrivate::PNGMemoryWriter writer;
return writer.create (getSurface ());
}
//-----------------------------------------------------------------------------
namespace CairoBitmapPrivate {
//-----------------------------------------------------------------------------
bool PixelAccess::init (Bitmap* inBitmap, const SurfaceHandle& inSurface)
{
cairo_surface_flush (inSurface);
address = cairo_image_surface_get_data (inSurface);
if (!address)
{
#if DEBUG
auto status = cairo_surface_status (inSurface);
if (status != CAIRO_STATUS_SUCCESS)
{
auto msg = cairo_status_to_string (status);
DebugPrint ("%s\n", msg);
}
#endif
return false;
}
surface = inSurface;
bitmap = inBitmap;
bytesPerRow = cairo_image_surface_get_stride (surface);
return true;
}
//-----------------------------------------------------------------------------
PixelAccess::~PixelAccess ()
{
cairo_surface_mark_dirty (surface);
bitmap->unlock ();
}
//-----------------------------------------------------------------------------
} // CairoBitmapPrivate
} // Cairo
} // VSTGUI
@@ -0,0 +1,62 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include <cairo/cairo.h>
#include "../../cpoint.h"
#include "../../vstguidebug.h"
#include "../iplatformbitmap.h"
#include "../platformfwd.h"
#include "cairoutils.h"
#include <functional>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
//-----------------------------------------------------------------------------
class Bitmap : public IPlatformBitmap
{
public:
static SharedPointer<Bitmap> create (UTF8StringPtr absolutePath);
static SharedPointer<Bitmap> create (const void* ptr, uint32_t memSize);
Bitmap ();
explicit Bitmap (const CPoint& size);
explicit Bitmap (const SurfaceHandle& surface);
~Bitmap () override;
bool load (const CResourceDescription& desc);
const CPoint& getSize () const override;
SharedPointer<IPlatformBitmapPixelAccess> lockPixels (bool alphaPremultiplied) override;
void setScaleFactor (double factor) override;
double getScaleFactor () const override;
PNGBitmapBuffer createMemoryPNGRepresentation () const;
const SurfaceHandle& getSurface () const
{
vstgui_assert (!locked, "Bitmap is locked");
if (locked)
{
static SurfaceHandle empty;
return empty;
}
return surface;
}
void unlock () { locked = false; }
private:
double scaleFactor {1.0};
SurfaceHandle surface;
CPoint size;
bool locked {false};
};
//------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,308 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cairofont.h"
#include "../../cstring.h"
#include "../../cfont.h"
#include "../../cpoint.h"
#include "../../ccolor.h"
#include "cairographicscontext.h"
#include "linuxstring.h"
#include "linuxfactory.h"
#include <pango/pangocairo.h>
#include <pango/pango-features.h>
#include <pango/pangofc-fontmap.h>
#include <fontconfig/fontconfig.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
namespace {
using PangoFontHandle = Handle<PangoFont*, decltype (&g_object_ref), g_object_ref,
decltype (&g_object_unref), g_object_unref>;
//------------------------------------------------------------------------
class FontList
{
public:
static FontList& instance ()
{
static FontList gInstance;
return gInstance;
}
FcConfig* getFontConfig () { return fcConfig; }
PangoFontMap* getFontMap () { return fontMap; }
PangoContext* getFontContext () { return fontContext; }
bool queryFont (UTF8StringPtr name, CCoord size, int32_t style, PangoFontHandle& fontHandle)
{
PangoFontDescription* desc = pango_font_description_new ();
pango_font_description_set_family_static (desc, name);
pango_font_description_set_absolute_size (desc, pango_units_from_double (size));
if (style & kItalicFace)
pango_font_description_set_style (desc, PANGO_STYLE_ITALIC);
if (style & kBoldFace)
pango_font_description_set_weight (desc, PANGO_WEIGHT_BOLD);
PangoFont* font = pango_font_map_load_font (fontMap, fontContext, desc);
pango_font_description_free (desc);
if (font)
fontHandle.assign (font);
return font != nullptr;
}
bool getAllFontFamilies (const FontFamilyCallback& callback)
{
if (!fontContext)
return false;
PangoFontFamily** families = nullptr;
int numFamilies = 0;
pango_context_list_families (fontContext, &families, &numFamilies);
for (int i = 0; i < numFamilies; ++i)
{
PangoFontFamily* family = families[i];
if (!callback (pango_font_family_get_name (family)))
break;
}
g_free (families);
return true;
}
private:
FontList ()
{
fontMap = pango_cairo_font_map_new ();
fontContext = pango_font_map_create_context (fontMap);
PangoFcFontMap* fcMap =
G_TYPE_CHECK_INSTANCE_CAST (fontMap, PANGO_TYPE_FC_FONT_MAP, PangoFcFontMap);
if (fcMap && FcInit () && (fcConfig = FcInitLoadConfigAndFonts ()))
{
if (auto linuxFactory = getPlatformFactory ().asLinuxFactory ())
{
const UTF8String& resourcePath = linuxFactory->getResourcePath ();
if (!resourcePath.empty ())
{
auto fontDir = resourcePath + "Fonts/";
FcConfigAppFontAddDir (fcConfig,
reinterpret_cast<const FcChar8*> (fontDir.data ()));
}
pango_fc_font_map_set_config (fcMap, fcConfig);
FcConfigDestroy (fcConfig);
}
}
}
~FontList ()
{
if (fontMap)
g_object_unref (fontMap);
if (fontContext)
g_object_unref (fontContext);
}
FontList (const FontList&) = delete;
FontList& operator= (const FontList&) = delete;
FcConfig* fcConfig = nullptr;
PangoFontMap* fontMap = nullptr;
PangoContext* fontContext = nullptr;
static int slantFromStyle (int32_t style)
{
return (style & kItalicFace) ? FC_SLANT_ITALIC : FC_SLANT_ROMAN;
}
static int weightFromStyle (int32_t style)
{
return (style & kBoldFace) ? FC_WEIGHT_BOLD : FC_WEIGHT_REGULAR;
}
};
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
struct Font::Impl
{
PangoFontHandle font;
int32_t style;
CCoord ascent {-1.};
CCoord descent {-1.};
CCoord leading {-1.};
CCoord capHeight {-1.};
};
//------------------------------------------------------------------------
Font::Font (UTF8StringPtr name, const CCoord& size, const int32_t& style)
{
impl = std::unique_ptr<Impl> (new Impl);
auto& fontList = FontList::instance ();
if (fontList.queryFont (name, size, style, impl->font))
{
PangoFontMetrics* metrics = pango_font_get_metrics (impl->font, nullptr);
if (metrics)
{
impl->ascent = pango_units_to_double (pango_font_metrics_get_ascent (metrics));
impl->descent = pango_units_to_double (pango_font_metrics_get_descent (metrics));
#if (PANGO_VERSION_MAJOR > 1) || ((PANGO_VERSION_MAJOR == 1) && PANGO_VERSION_MINOR >= 44)
auto height = pango_units_to_double (pango_font_metrics_get_height (metrics));
impl->leading = height - (impl->ascent + impl->descent);
#else
impl->leading = 0.;
#endif
pango_font_metrics_unref (metrics);
}
PangoContext* context = fontList.getFontContext ();
if (context)
{
PangoLayout* layout = pango_layout_new (context);
if (layout)
{
PangoFontDescription* desc = pango_font_describe (impl->font);
if (desc)
{
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
}
pango_layout_set_text (layout, "M", -1);
PangoRectangle inkExtents {};
pango_layout_get_pixel_extents (layout, &inkExtents, nullptr);
impl->capHeight = inkExtents.height;
g_object_unref (layout);
}
}
}
impl->style = style;
}
//------------------------------------------------------------------------
Font::~Font () {}
//------------------------------------------------------------------------
bool Font::valid () const { return impl->font; }
//------------------------------------------------------------------------
double Font::getAscent () const { return impl->ascent; }
//------------------------------------------------------------------------
double Font::getDescent () const { return impl->descent; }
//------------------------------------------------------------------------
double Font::getLeading () const { return impl->leading; }
//------------------------------------------------------------------------
double Font::getCapHeight () const { return impl->capHeight; }
//------------------------------------------------------------------------
const IFontPainter* Font::getPainter () const { return this; }
//------------------------------------------------------------------------
void Font::drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string,
const CPoint& p, const CColor& color, bool antialias) const
{
auto cairoContext = std::dynamic_pointer_cast<CairoGraphicsDeviceContext> (context);
if (!cairoContext)
return;
auto linuxString = dynamic_cast<LinuxString*> (string);
if (!linuxString)
return;
PangoContext* pangoContext = FontList::instance ().getFontContext ();
if (!pangoContext)
return;
PangoLayout* layout = pango_layout_new (pangoContext);
if (!layout)
return;
if (impl->font)
{
PangoFontDescription* desc = pango_font_describe (impl->font);
if (desc)
{
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
}
}
PangoAttrList* attrs = pango_attr_list_new ();
if (attrs)
{
if (impl->style & kUnderlineFace)
pango_attr_list_insert (attrs, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE));
if (impl->style & kStrikethroughFace)
pango_attr_list_insert (attrs, pango_attr_strikethrough_new (true));
pango_layout_set_attributes (layout, attrs);
pango_attr_list_unref (attrs);
}
pango_layout_set_text (layout, linuxString->get ().c_str (), -1);
PangoRectangle extents {};
pango_layout_get_pixel_extents (layout, nullptr, &extents);
PangoLayoutIter* iter = pango_layout_get_iter (layout);
CCoord baseline = 0.0;
if (iter)
{
baseline = pango_units_to_double (pango_layout_iter_get_baseline (iter));
pango_layout_iter_free (iter);
}
cairoContext->drawPangoLayout (layout, {p.x + extents.x, p.y + extents.y - baseline}, color);
g_object_unref (layout);
}
//------------------------------------------------------------------------
CCoord Font::getStringWidth (const PlatformGraphicsDeviceContextPtr&, IPlatformString* string,
bool antialias) const
{
if (auto linuxString = dynamic_cast<LinuxString*> (string))
{
int pangoWidth = 0;
PangoContext* context = FontList::instance ().getFontContext ();
if (context)
{
PangoLayout* layout = pango_layout_new (context);
if (layout)
{
if (impl->font)
{
PangoFontDescription* desc = pango_font_describe (impl->font);
if (desc)
{
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
}
}
pango_layout_set_text (layout, linuxString->get ().c_str (), -1);
pango_layout_get_pixel_size (layout, &pangoWidth, nullptr);
g_object_unref (layout);
}
}
return pangoWidth;
}
return 0;
}
//------------------------------------------------------------------------
bool Font::getAllFamilies (const FontFamilyCallback& callback)
{
return Cairo::FontList::instance ().getAllFontFamilies (callback);
}
//------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,46 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformfont.h"
#include "../platformfactory.h"
#include <memory>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
//------------------------------------------------------------------------
class Font
: public IPlatformFont
, public IFontPainter
{
public:
Font (UTF8StringPtr name, const CCoord& size, const int32_t& style);
~Font ();
bool valid () const;
double getAscent () const override;
double getDescent () const override;
double getLeading () const override;
double getCapHeight () const override;
const IFontPainter* getPainter () const override;
void drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string,
const CPoint& p, const CColor& color, bool antialias = true) const override;
CCoord getStringWidth (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string,
bool antialias = true) const override;
static bool getAllFamilies (const FontFamilyCallback& callback);
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,67 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cairogradient.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
//------------------------------------------------------------------------
Gradient::~Gradient () noexcept
{
changed ();
}
//------------------------------------------------------------------------
void Gradient::changed ()
{
linearGradient.reset ();
radialGradient.reset ();
}
//------------------------------------------------------------------------
const PatternHandle& Gradient::getLinearGradient (CPoint start, CPoint end) const
{
if (!linearGradient || start != linearGradientStart || end != linearGradientEnd)
{
linearGradient.reset ();
radialGradient.reset ();
linearGradientStart = start;
linearGradientEnd = end;
linearGradient =
PatternHandle (cairo_pattern_create_linear (start.x, start.y, end.x, end.y));
for (auto& it : getColorStops ())
{
cairo_pattern_add_color_stop_rgba (
linearGradient, it.first, it.second.normRed<double> (),
it.second.normGreen<double> (), it.second.normBlue<double> (),
it.second.normAlpha<double> ());
}
}
return linearGradient;
}
//------------------------------------------------------------------------
const PatternHandle& Gradient::getRadialGradient (CPoint center, CCoord radius,
CPoint originOffset) const
{
if (!radialGradient)
{
radialGradient = PatternHandle (
cairo_pattern_create_radial (center.x, center.y, 0., center.x, center.y, radius));
for (auto& it : getColorStops ())
{
cairo_pattern_add_color_stop_rgba (
radialGradient, it.first, it.second.normRed<double> (),
it.second.normGreen<double> (), it.second.normBlue<double> (),
it.second.normAlpha<double> ());
}
}
return radialGradient;
}
//------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,39 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../common/gradientbase.h"
#include "../../cpoint.h"
#include "cairoutils.h"
#include <cairo/cairo.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
//------------------------------------------------------------------------
class Gradient : public PlatformGradientBase
{
public:
~Gradient () noexcept override;
const PatternHandle& getLinearGradient (CPoint start, CPoint end) const;
const PatternHandle& getRadialGradient (CPoint center, CCoord radius,
CPoint originOffset) const;
private:
void changed () override;
/* we want to calculate a normalized linear and radial gradiant */
mutable PatternHandle linearGradient;
mutable PatternHandle radialGradient;
mutable CPoint linearGradientStart;
mutable CPoint linearGradientEnd;
};
//------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,751 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cairographicscontext.h"
#include "cairobitmap.h"
#include "cairopath.h"
#include "cairogradient.h"
#include "../../crect.h"
#include "../../cgraphicstransform.h"
#include "../../ccolor.h"
#include "../../cdrawdefs.h"
#include "../../clinestyle.h"
#include <pango/pangocairo.h>
#include <stack>
//------------------------------------------------------------------------
namespace VSTGUI {
using TransformMatrix = CGraphicsTransform;
//------------------------------------------------------------------------
inline void checkCairoStatus (const Cairo::ContextHandle& handle)
{
#if DEBUG
auto status = cairo_status (handle);
if (status != CAIRO_STATUS_SUCCESS)
{
auto msg = cairo_status_to_string (status);
DebugPrint ("%s\n", msg);
}
#endif
}
//------------------------------------------------------------------------
inline cairo_matrix_t convert (const TransformMatrix& ct)
{
return {ct.m11, ct.m21, ct.m12, ct.m22, ct.dx, ct.dy};
}
//-----------------------------------------------------------------------------
struct CairoGraphicsDeviceFactory::Impl
{
std::vector<std::shared_ptr<CairoGraphicsDevice>> devices;
};
//-----------------------------------------------------------------------------
CairoGraphicsDeviceFactory::CairoGraphicsDeviceFactory ()
{
impl = std::make_unique<Impl> ();
}
//-----------------------------------------------------------------------------
CairoGraphicsDeviceFactory::~CairoGraphicsDeviceFactory () noexcept = default;
//-----------------------------------------------------------------------------
PlatformGraphicsDevicePtr CairoGraphicsDeviceFactory::getDeviceForScreen (ScreenInfo::Identifier screen) const
{
if (impl->devices.empty ())
{
// just create a dummy device as we don't really need the cairo device at the moment
impl->devices.push_back (std::make_shared<CairoGraphicsDevice> (nullptr));
}
return impl->devices.front ();
}
//-----------------------------------------------------------------------------
PlatformGraphicsDevicePtr CairoGraphicsDeviceFactory::addDevice (cairo_device_t* device)
{
for (auto& dev : impl->devices)
{
if (dev->get () == device)
return dev;
}
impl->devices.push_back (std::make_shared<CairoGraphicsDevice> (device));
return impl->devices.back ();
}
//-----------------------------------------------------------------------------
void CairoGraphicsDeviceFactory::removeDevice (cairo_device_t* device)
{
const auto citer = std::find_if (impl->devices.cbegin (), impl->devices.cend (),
[device] (const auto& dev) { return dev->get () == device; });
if (citer == impl->devices.cend ())
return;
impl->devices.erase (citer);
}
//-----------------------------------------------------------------------------
struct CairoGraphicsDevice::Impl
{
cairo_device_t* device;
};
//-----------------------------------------------------------------------------
CairoGraphicsDevice::CairoGraphicsDevice (cairo_device_t* device)
{
impl = std::make_unique<Impl> ();
impl->device = device;
if (device)
cairo_device_reference (device);
}
//-----------------------------------------------------------------------------
CairoGraphicsDevice::~CairoGraphicsDevice () noexcept
{
if (impl->device)
{
cairo_device_destroy (impl->device);
}
}
//-----------------------------------------------------------------------------
cairo_device_t* CairoGraphicsDevice::get () const { return impl->device; }
//-----------------------------------------------------------------------------
PlatformGraphicsDeviceContextPtr
CairoGraphicsDevice::createBitmapContext (const PlatformBitmapPtr& bitmap) const
{
auto cairoBitmap = bitmap.cast<Cairo::Bitmap> ();
if (cairoBitmap)
{
return std::make_shared<CairoGraphicsDeviceContext> (*this, cairoBitmap->getSurface ());
}
return nullptr;
}
//-----------------------------------------------------------------------------
inline CPoint pixelAlign (const CGraphicsTransform& tm, const CPoint& p)
{
auto obj = p;
tm.transform (obj);
obj.x = std::round (obj.x);
obj.y = std::round (obj.y);
tm.inverse ().transform (obj);
return obj;
}
//-----------------------------------------------------------------------------
inline CRect pixelAlign (const CGraphicsTransform& tm, const CRect& r)
{
auto obj = r;
tm.transform (obj);
obj.left = std::round (obj.left);
obj.right = std::round (obj.right);
obj.top = std::round (obj.top);
obj.bottom = std::round (obj.bottom);
tm.inverse ().transform (obj);
return obj;
}
//------------------------------------------------------------------------
struct CairoGraphicsDeviceContext::Impl
{
const CairoGraphicsDevice& device;
Cairo::ContextHandle context;
Cairo::SurfaceHandle surface;
Impl (const CairoGraphicsDevice& device, const Cairo::SurfaceHandle& surface)
: device (device), surface (surface)
{
context.assign (cairo_create (surface));
}
template<typename Proc>
void doInContext (Proc p)
{
auto ct = state.tm;
CRect clip = state.clip;
if (clip.isEmpty ())
return;
cairo_save (context);
cairo_rectangle (context, clip.left, clip.top, clip.getWidth (), clip.getHeight ());
cairo_clip (context);
auto matrix = convert (ct);
cairo_set_matrix (context, &matrix);
auto antialiasMode = state.drawMode.modeIgnoringIntegralMode () == kAntiAliasing
? CAIRO_ANTIALIAS_BEST
: CAIRO_ANTIALIAS_NONE;
cairo_set_antialias (context, antialiasMode);
p ();
checkCairoStatus (context);
cairo_restore (context);
}
void applyLineWidthCTM ()
{
auto p = calcLineTranslate ();
cairo_translate (context, p.x, p.y);
}
CPoint calcLineTranslate () const
{
CPoint p {};
int32_t lineWidthInt = static_cast<int32_t> (state.lineWidth);
if (static_cast<CCoord> (lineWidthInt) == state.lineWidth && lineWidthInt % 2)
p.x = p.y = 0.5;
return p;
}
void applyLineStyle ()
{
auto lineWidth = state.lineWidth;
cairo_set_line_width (context, lineWidth);
const auto& style = state.lineStyle;
if (!style.getDashLengths ().empty ())
{
auto lengths = style.getDashLengths ();
for (auto& l : lengths)
l *= lineWidth;
cairo_set_dash (context, lengths.data (), lengths.size (), style.getDashPhase ());
}
cairo_line_cap_t lineCap;
switch (style.getLineCap ())
{
case CLineStyle::kLineCapButt:
{
lineCap = CAIRO_LINE_CAP_BUTT;
break;
}
case CLineStyle::kLineCapRound:
{
lineCap = CAIRO_LINE_CAP_ROUND;
break;
}
case CLineStyle::kLineCapSquare:
{
lineCap = CAIRO_LINE_CAP_SQUARE;
break;
}
}
cairo_set_line_cap (context, lineCap);
cairo_line_join_t lineJoin;
switch (style.getLineJoin ())
{
case CLineStyle::kLineJoinBevel:
{
lineJoin = CAIRO_LINE_JOIN_BEVEL;
break;
}
case CLineStyle::kLineJoinMiter:
{
lineJoin = CAIRO_LINE_JOIN_MITER;
break;
}
case CLineStyle::kLineJoinRound:
{
lineJoin = CAIRO_LINE_JOIN_ROUND;
break;
}
}
cairo_set_line_join (context, lineJoin);
}
void setupSourceColor (CColor color)
{
auto alpha = color.normAlpha<double> ();
alpha *= state.globalAlpha;
cairo_set_source_rgba (context, color.normRed<double> (), color.normGreen<double> (),
color.normBlue<double> (), alpha);
checkCairoStatus (context);
}
void applyFillColor () { setupSourceColor (state.fillColor); }
void applyFrameColor () { setupSourceColor (state.frameColor); }
void applyFontColor (CColor color) { setupSourceColor (color); }
void draw (PlatformGraphicsDrawStyle drawStyle)
{
switch (drawStyle)
{
case PlatformGraphicsDrawStyle::Stroked:
{
applyLineStyle ();
applyFrameColor ();
cairo_stroke (context);
break;
}
case PlatformGraphicsDrawStyle::Filled:
{
applyFillColor ();
cairo_fill (context);
break;
}
case PlatformGraphicsDrawStyle::FilledAndStroked:
{
applyFillColor ();
cairo_fill_preserve (context);
applyLineStyle ();
applyFrameColor ();
cairo_stroke (context);
break;
}
}
checkCairoStatus (context);
}
struct State
{
CRect clip {};
CLineStyle lineStyle {kLineSolid};
CDrawMode drawMode {};
CColor fillColor {kTransparentCColor};
CColor frameColor {kTransparentCColor};
CCoord lineWidth {1.};
double globalAlpha {1.};
TransformMatrix tm {};
};
State state;
std::stack<State> stateStack;
double scaleFactor {1.};
PlatformGraphicsPathFactoryPtr pathFactory;
};
//------------------------------------------------------------------------
CairoGraphicsDeviceContext::CairoGraphicsDeviceContext (const CairoGraphicsDevice& device,
const Cairo::SurfaceHandle& handle)
{
impl = std::make_unique<Impl> (device, handle);
}
//------------------------------------------------------------------------
CairoGraphicsDeviceContext::~CairoGraphicsDeviceContext () noexcept {}
//------------------------------------------------------------------------
const IPlatformGraphicsDevice& CairoGraphicsDeviceContext::getDevice () const
{
return impl->device;
}
//------------------------------------------------------------------------
PlatformGraphicsPathFactoryPtr CairoGraphicsDeviceContext::getGraphicsPathFactory () const
{
if (!impl->pathFactory)
impl->pathFactory = std::make_shared<Cairo::GraphicsPathFactory> (impl->context);
return impl->pathFactory;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::beginDraw () const
{
if (impl->context)
cairo_save (impl->context);
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::endDraw () const
{
if (impl->context)
cairo_restore (impl->context);
if (impl->surface)
cairo_surface_flush (impl->surface);
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawLine (LinePair line) const
{
impl->doInContext ([&] () {
impl->applyLineStyle ();
impl->applyFrameColor ();
if (impl->state.drawMode.integralMode ())
{
CPoint start = pixelAlign (impl->state.tm, line.first);
CPoint end = pixelAlign (impl->state.tm, line.second);
impl->applyLineWidthCTM ();
cairo_move_to (impl->context, start.x, start.y);
cairo_line_to (impl->context, end.x, end.y);
}
else
{
cairo_move_to (impl->context, line.first.x, line.first.y);
cairo_line_to (impl->context, line.second.x, line.second.y);
}
cairo_stroke (impl->context);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawLines (const LineList& lines) const
{
impl->doInContext ([&] () {
impl->applyLineStyle ();
impl->applyFrameColor ();
if (impl->state.drawMode.integralMode ())
{
auto pt = impl->calcLineTranslate ();
for (auto& line : lines)
{
CPoint start = pixelAlign (impl->state.tm, line.first);
CPoint end = pixelAlign (impl->state.tm, line.second);
cairo_move_to (impl->context, start.x + pt.x, start.y + pt.y);
cairo_line_to (impl->context, end.x + pt.x, end.y + pt.y);
cairo_stroke (impl->context);
}
}
else
{
for (auto& line : lines)
{
cairo_move_to (impl->context, line.first.x, line.first.y);
cairo_line_to (impl->context, line.second.x, line.second.y);
cairo_stroke (impl->context);
}
}
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawPolygon (const PointList& polygonPointList,
PlatformGraphicsDrawStyle drawStyle) const
{
vstgui_assert (polygonPointList.empty () == false);
impl->doInContext ([&] () {
bool doPixelAlign = impl->state.drawMode.integralMode ();
auto last = polygonPointList.back ();
if (doPixelAlign)
last = pixelAlign (impl->state.tm, last);
cairo_move_to (impl->context, last.x, last.y);
for (auto p : polygonPointList)
{
if (doPixelAlign)
p = pixelAlign (impl->state.tm, p);
cairo_line_to (impl->context, p.x, p.y);
}
impl->draw (drawStyle);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const
{
impl->doInContext ([&] () {
if (drawStyle != PlatformGraphicsDrawStyle::Filled)
{
rect.right -= 1.;
rect.bottom -= 1.;
}
if (impl->state.drawMode.integralMode ())
{
rect = pixelAlign (impl->state.tm, rect);
if (drawStyle != PlatformGraphicsDrawStyle::Filled)
impl->applyLineWidthCTM ();
cairo_rectangle (impl->context, rect.left, rect.top, rect.getWidth (),
rect.getHeight ());
}
else
{
cairo_rectangle (impl->context, rect.left + 0.5, rect.top + 0.5, rect.getWidth () - 0.5,
rect.getHeight () - 0.5);
}
impl->draw (drawStyle);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawArc (CRect rect, double startAngle1, double endAngle2,
PlatformGraphicsDrawStyle drawStyle) const
{
impl->doInContext ([&] () {
CPoint center = rect.getCenter ();
cairo_translate (impl->context, center.x, center.y);
cairo_scale (impl->context, 2.0 / rect.getWidth (), 2.0 / rect.getHeight ());
cairo_arc (impl->context, 0, 0, 1, startAngle1, endAngle2);
impl->draw (drawStyle);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const
{
impl->doInContext ([&] () {
CPoint center = rect.getCenter ();
cairo_translate (impl->context, center.x, center.y);
cairo_scale (impl->context, 2.0 / rect.getWidth (), 2.0 / rect.getHeight ());
cairo_arc (impl->context, 0, 0, 1, 0, 2 * M_PI);
impl->draw (drawStyle);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawPoint (CPoint point, CColor color) const { return false; }
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset,
double alpha, BitmapInterpolationQuality quality) const
{
auto cairoBitmap = dynamic_cast<Cairo::Bitmap*> (&bitmap);
if (!cairoBitmap)
return false;
impl->doInContext ([&] () {
cairo_translate (impl->context, dest.left, dest.top);
cairo_rectangle (impl->context, 0, 0, dest.getWidth (), dest.getHeight ());
cairo_clip (impl->context);
// Setup a pattern for scaling bitmaps and take it as source afterwards.
auto pattern = cairo_pattern_create_for_surface (cairoBitmap->getSurface ());
cairo_matrix_t matrix;
cairo_pattern_get_matrix (pattern, &matrix);
cairo_matrix_init_scale (&matrix, cairoBitmap->getScaleFactor (),
cairoBitmap->getScaleFactor ());
cairo_matrix_translate (&matrix, offset.x, offset.y);
cairo_pattern_set_matrix (pattern, &matrix);
cairo_set_source (impl->context, pattern);
cairo_rectangle (impl->context, -offset.x, -offset.y, dest.getWidth () + offset.x,
dest.getHeight () + offset.y);
alpha *= impl->state.globalAlpha;
if (alpha != 1.f)
{
cairo_paint_with_alpha (impl->context, alpha);
}
else
{
cairo_fill (impl->context);
}
cairo_pattern_destroy (pattern);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::clearRect (CRect rect) const
{
impl->doInContext ([&] () {
cairo_set_operator (impl->context, CAIRO_OPERATOR_CLEAR);
cairo_rectangle (impl->context, rect.left, rect.top, rect.getWidth (), rect.getHeight ());
cairo_fill (impl->context);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::drawGraphicsPath (IPlatformGraphicsPath& path,
PlatformGraphicsPathDrawMode mode,
TransformMatrix* transformation) const
{
auto cairoPath = dynamic_cast<Cairo::GraphicsPath*> (&path);
if (!cairoPath)
return false;
impl->doInContext ([&] () {
std::unique_ptr<Cairo::GraphicsPath> alignedPath;
if (impl->state.drawMode.integralMode ())
{
alignedPath = cairoPath->copyPixelAlign ([&] (CPoint p) {
p = pixelAlign (impl->state.tm, p);
return p;
});
}
auto p = alignedPath ? alignedPath->getCairoPath () : cairoPath->getCairoPath ();
if (transformation)
{
cairo_matrix_t currentMatrix;
cairo_matrix_t resultMatrix;
auto matrix = convert (*transformation);
cairo_get_matrix (impl->context, &currentMatrix);
cairo_matrix_multiply (&resultMatrix, &matrix, &currentMatrix);
cairo_set_matrix (impl->context, &resultMatrix);
}
cairo_append_path (impl->context, p);
switch (mode)
{
case PlatformGraphicsPathDrawMode::Filled:
{
impl->applyFillColor ();
cairo_fill (impl->context);
break;
}
case PlatformGraphicsPathDrawMode::FilledEvenOdd:
{
impl->applyFillColor ();
cairo_set_fill_rule (impl->context, CAIRO_FILL_RULE_EVEN_ODD);
cairo_fill (impl->context);
break;
}
case PlatformGraphicsPathDrawMode::Stroked:
{
impl->applyLineStyle ();
impl->applyFrameColor ();
cairo_stroke (impl->context);
break;
}
}
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::fillLinearGradient (IPlatformGraphicsPath& path,
const IPlatformGradient& gradient,
CPoint startPoint, CPoint endPoint,
bool evenOdd,
TransformMatrix* transformation) const
{
auto cairoPath = dynamic_cast<Cairo::GraphicsPath*> (&path);
if (!cairoPath)
return false;
auto cairoGradient = dynamic_cast<const Cairo::Gradient*> (&gradient);
if (!cairoGradient)
return false;
impl->doInContext ([&] () {
std::unique_ptr<Cairo::GraphicsPath> alignedPath;
if (impl->state.drawMode.integralMode ())
{
alignedPath = cairoPath->copyPixelAlign ([&] (CPoint p) {
p = pixelAlign (impl->state.tm, p);
return p;
});
}
auto p = alignedPath ? alignedPath->getCairoPath () : cairoPath->getCairoPath ();
cairo_append_path (impl->context, p);
cairo_set_source (impl->context, cairoGradient->getLinearGradient (startPoint, endPoint));
if (evenOdd)
cairo_set_fill_rule (impl->context, CAIRO_FILL_RULE_EVEN_ODD);
cairo_fill (impl->context);
});
return true;
}
//------------------------------------------------------------------------
bool CairoGraphicsDeviceContext::fillRadialGradient (IPlatformGraphicsPath& path,
const IPlatformGradient& gradient,
CPoint center, CCoord radius,
CPoint originOffset, bool evenOdd,
TransformMatrix* transformation) const
{
auto cairoPath = dynamic_cast<Cairo::GraphicsPath*> (&path);
if (!cairoPath)
return false;
auto cairoGradient = dynamic_cast<const Cairo::Gradient*> (&gradient);
if (!cairoGradient)
return false;
impl->doInContext ([&] () {
std::unique_ptr<Cairo::GraphicsPath> alignedPath;
if (impl->state.drawMode.integralMode ())
{
alignedPath = cairoPath->copyPixelAlign ([&] (CPoint p) {
p = pixelAlign (impl->state.tm, p);
return p;
});
}
auto p = alignedPath ? alignedPath->getCairoPath () : cairoPath->getCairoPath ();
cairo_append_path (impl->context, p);
const auto& radialGradient =
cairoGradient->getRadialGradient (center, radius, originOffset);
cairo_set_source (impl->context, radialGradient);
if (evenOdd)
cairo_set_fill_rule (impl->context, CAIRO_FILL_RULE_EVEN_ODD);
cairo_arc (impl->context, 0, 0, 0, 0., M_PI * 2.);
cairo_fill (impl->context);
});
return true;
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::saveGlobalState () const
{
cairo_save (impl->context);
impl->stateStack.push (impl->state);
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::restoreGlobalState () const
{
vstgui_assert (impl->stateStack.empty () == false,
"Unbalanced calls to saveGlobalState and restoreGlobalState");
#if NDEBUG
if (impl->stateStack.empty ())
return;
#endif
cairo_restore (impl->context);
impl->state = impl->stateStack.top ();
impl->stateStack.pop ();
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setLineStyle (const CLineStyle& style) const
{
impl->state.lineStyle = style;
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setLineWidth (CCoord width) const
{
impl->state.lineWidth = width;
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setDrawMode (CDrawMode mode) const { impl->state.drawMode = mode; }
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setClipRect (CRect clip) const { impl->state.clip = clip; }
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setFillColor (CColor color) const
{
impl->state.fillColor = color;
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setFrameColor (CColor color) const
{
impl->state.frameColor = color;
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setGlobalAlpha (double newAlpha) const
{
impl->state.globalAlpha = newAlpha;
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::setTransformMatrix (const TransformMatrix& tm) const
{
impl->state.tm = tm;
}
//------------------------------------------------------------------------
const IPlatformGraphicsDeviceContextBitmapExt* CairoGraphicsDeviceContext::asBitmapExt () const
{
return nullptr;
}
//------------------------------------------------------------------------
void CairoGraphicsDeviceContext::drawPangoLayout (void* layout, CPoint pos, CColor color) const
{
impl->doInContext ([&] () {
impl->applyFontColor (color);
cairo_move_to (impl->context, pos.x, pos.y);
pango_cairo_show_layout (impl->context, reinterpret_cast<PangoLayout*> (layout));
});
}
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,108 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformgraphicsdevice.h"
#include "cairoutils.h"
//------------------------------------------------------------------------
namespace VSTGUI {
class CairoGraphicsDevice;
//------------------------------------------------------------------------
class CairoGraphicsDeviceContext : public IPlatformGraphicsDeviceContext
{
public:
CairoGraphicsDeviceContext (const CairoGraphicsDevice& device,
const Cairo::SurfaceHandle& handle);
~CairoGraphicsDeviceContext () noexcept;
const IPlatformGraphicsDevice& getDevice () const override;
PlatformGraphicsPathFactoryPtr getGraphicsPathFactory () const override;
bool beginDraw () const override;
bool endDraw () const override;
// draw commands
bool drawLine (LinePair line) const override;
bool drawLines (const LineList& lines) const override;
bool drawPolygon (const PointList& polygonPointList,
PlatformGraphicsDrawStyle drawStyle) const override;
bool drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override;
bool drawArc (CRect rect, double startAngle1, double endAngle2,
PlatformGraphicsDrawStyle drawStyle) const override;
bool drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override;
bool drawPoint (CPoint point, CColor color) const override;
bool drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, double alpha,
BitmapInterpolationQuality quality) const override;
bool clearRect (CRect rect) const override;
bool drawGraphicsPath (IPlatformGraphicsPath& path, PlatformGraphicsPathDrawMode mode,
TransformMatrix* transformation) const override;
bool fillLinearGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient,
CPoint startPoint, CPoint endPoint, bool evenOdd,
TransformMatrix* transformation) const override;
bool fillRadialGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient,
CPoint center, CCoord radius, CPoint originOffset, bool evenOdd,
TransformMatrix* transformation) const override;
// state
void saveGlobalState () const override;
void restoreGlobalState () const override;
void setLineStyle (const CLineStyle& style) const override;
void setLineWidth (CCoord width) const override;
void setDrawMode (CDrawMode mode) const override;
void setClipRect (CRect clip) const override;
void setFillColor (CColor color) const override;
void setFrameColor (CColor color) const override;
void setGlobalAlpha (double newAlpha) const override;
void setTransformMatrix (const TransformMatrix& tm) const override;
// extension
const IPlatformGraphicsDeviceContextBitmapExt* asBitmapExt () const override;
// private
void drawPangoLayout (void* layout, CPoint pos, CColor color) const;
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
class CairoGraphicsDevice : public IPlatformGraphicsDevice
{
public:
CairoGraphicsDevice (cairo_device_t* device);
~CairoGraphicsDevice () noexcept;
PlatformGraphicsDeviceContextPtr
createBitmapContext (const PlatformBitmapPtr& bitmap) const override;
cairo_device_t* get () const;
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
class CairoGraphicsDeviceFactory : public IPlatformGraphicsDeviceFactory
{
public:
CairoGraphicsDeviceFactory ();
~CairoGraphicsDeviceFactory () noexcept;
PlatformGraphicsDevicePtr getDeviceForScreen (ScreenInfo::Identifier screen) const override;
PlatformGraphicsDevicePtr addDevice (cairo_device_t* device);
void removeDevice (cairo_device_t* device);
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,195 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../../cgradient.h"
#include "../../cgraphicstransform.h"
#include "cairopath.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
//-----------------------------------------------------------------------------
GraphicsPathFactory::GraphicsPathFactory (const ContextHandle& cr) : context (cr) {}
//-----------------------------------------------------------------------------
PlatformGraphicsPathPtr
GraphicsPathFactory::createPath ([[maybe_unused]] PlatformGraphicsPathFillMode fillMode)
{
return std::make_unique<GraphicsPath> (context);
}
//-----------------------------------------------------------------------------
PlatformGraphicsPathPtr GraphicsPathFactory::createTextPath (const PlatformFontPtr& font,
UTF8StringPtr text)
{
return nullptr;
}
//------------------------------------------------------------------------
GraphicsPath::GraphicsPath (const ContextHandle& c) : context (c)
{
cairo_save (context);
cairo_new_path (context);
}
//------------------------------------------------------------------------
GraphicsPath::~GraphicsPath () noexcept
{
cairo_path_destroy (path);
}
//------------------------------------------------------------------------
void GraphicsPath::finishBuilding ()
{
path = cairo_copy_path (context);
cairo_restore (context);
cairo_new_path (context); // clear path in context
}
//------------------------------------------------------------------------
void GraphicsPath::addArc (const CRect& rect, double startAngle, double endAngle, bool clockwise)
{
auto radiusX = (rect.right - rect.left) / 2.;
auto radiusY = (rect.bottom - rect.top) / 2.;
auto centerX = static_cast<double> (rect.left + radiusX);
auto centerY = static_cast<double> (rect.top + radiusY);
startAngle = radians (startAngle);
endAngle = radians (endAngle);
if (radiusX != radiusY)
{
startAngle = std::atan2 (std::sin (startAngle) * radiusX, std::cos (startAngle) * radiusY);
endAngle = std::atan2 (std::sin (endAngle) * radiusX, std::cos (endAngle) * radiusY);
}
cairo_matrix_t matrix;
cairo_get_matrix (context, &matrix);
cairo_translate (context, centerX, centerY);
cairo_scale (context, radiusX, radiusY);
if (clockwise)
{
cairo_arc (context, 0, 0, 1, startAngle, endAngle);
}
else
{
cairo_arc_negative (context, 0, 0, 1, startAngle, endAngle);
}
cairo_set_matrix (context, &matrix);
}
//------------------------------------------------------------------------
void GraphicsPath::addEllipse (const CRect& rect) { addArc (rect, 0, 360, true); }
//------------------------------------------------------------------------
void GraphicsPath::addRect (const CRect& rect)
{
cairo_rectangle (context, rect.left, rect.top, rect.getWidth (), rect.getHeight ());
}
//------------------------------------------------------------------------
void GraphicsPath::addLine (const CPoint& to)
{
cairo_line_to (context, to.x, to.y);
}
//------------------------------------------------------------------------
void GraphicsPath::addBezierCurve (const CPoint& control1, const CPoint& control2,
const CPoint& end)
{
cairo_curve_to (context, control1.x, control1.y, control2.x, control2.y, end.x, end.y);
}
//------------------------------------------------------------------------
void GraphicsPath::beginSubpath (const CPoint& start)
{
cairo_new_sub_path (context);
cairo_move_to (context, start.x, start.y);
}
//------------------------------------------------------------------------
void GraphicsPath::closeSubpath ()
{
cairo_close_path (context);
}
//------------------------------------------------------------------------
std::unique_ptr<GraphicsPath> GraphicsPath::copyPixelAlign (const std::function<CPoint (CPoint)>& pixelAlignFunc)
{
auto result = std::make_unique<GraphicsPath> (context);
cairo_append_path (context, path);
result->finishBuilding ();
auto rpath = result->path;
auto align = [&] (_cairo_path_data_t* data, int index) {
CPoint input (data[index].point.x, data[index].point.y);
auto output = pixelAlignFunc (input);
data[index].point.x = output.x;
data[index].point.y = output.y;
};
for (auto i = 0; i < rpath->num_data; i += rpath->data[i].header.length)
{
auto data = &rpath->data[i];
switch (data->header.type)
{
case CAIRO_PATH_MOVE_TO:
{
align (data, 1);
break;
}
case CAIRO_PATH_LINE_TO:
{
align (data, 1);
break;
}
case CAIRO_PATH_CURVE_TO:
{
align (data, 1);
align (data, 2);
align (data, 3);
break;
}
case CAIRO_PATH_CLOSE_PATH: { break;
}
}
}
return result;
}
//------------------------------------------------------------------------
bool GraphicsPath::hitTest (const CPoint& p, bool evenOddFilled,
CGraphicsTransform* transform) const
{
auto tp = p;
if (transform)
transform->transform (tp);
cairo_save (context);
cairo_new_path (context);
cairo_append_path (context, path);
cairo_set_fill_rule (context,
evenOddFilled ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
cairo_clip (context);
auto result = cairo_in_clip (context, tp.x, tp.y);
cairo_restore (context);
return result;
}
//------------------------------------------------------------------------
CRect GraphicsPath::getBoundingBox () const
{
CRect r;
cairo_save (context);
cairo_new_path (context);
cairo_append_path (context, path);
CPoint p1, p2;
cairo_path_extents (context, &p1.x, &p1.y, &p2.x, &p2.y);
cairo_restore (context);
r.setTopLeft (p1);
r.setBottomRight (p2);
return r;
}
//------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,65 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../cgraphicspath.h"
#include "../iplatformgraphicspath.h"
#include "cairoutils.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
//------------------------------------------------------------------------
class GraphicsPathFactory : public IPlatformGraphicsPathFactory
{
public:
GraphicsPathFactory (const ContextHandle& cr);
PlatformGraphicsPathPtr createPath (PlatformGraphicsPathFillMode fillMode) override;
PlatformGraphicsPathPtr createTextPath (const PlatformFontPtr& font,
UTF8StringPtr text) override;
private:
ContextHandle context;
};
//-----------------------------------------------------------------------------
class GraphicsPath : public IPlatformGraphicsPath
{
public:
GraphicsPath (const ContextHandle& c);
~GraphicsPath () noexcept;
cairo_path_t* getCairoPath () const { return path; }
std::unique_ptr<GraphicsPath>
copyPixelAlign (const std::function<CPoint (CPoint)>& pixelAlignFunc);
// IPlatformGraphicsPath
void addArc (const CRect& rect, double startAngle, double endAngle, bool clockwise) override;
void addEllipse (const CRect& rect) override;
void addRect (const CRect& rect) override;
void addLine (const CPoint& to) override;
void addBezierCurve (const CPoint& control1, const CPoint& control2,
const CPoint& end) override;
void beginSubpath (const CPoint& start) override;
void closeSubpath () override;
void finishBuilding () override;
bool hitTest (const CPoint& p, bool evenOddFilled = false,
CGraphicsTransform* transform = nullptr) const override;
CRect getBoundingBox () const override;
PlatformGraphicsPathFillMode getFillMode () const override
{
return PlatformGraphicsPathFillMode::Ignored;
}
private:
ContextHandle context;
cairo_path_t* path {nullptr};
};
//------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,81 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include <cairo/cairo.h>
#include <utility>
//-----------------------------------------------------------------------------
namespace VSTGUI {
namespace Cairo {
//-----------------------------------------------------------------------------
template <typename Type, typename RetainProcType, RetainProcType RetainProc,
typename ReleaseProcType, ReleaseProcType ReleaseProc>
class Handle
{
public:
Handle () {}
explicit Handle (Type h) : handle (h) {}
~Handle () { reset (); }
Handle (Handle&& o) { *this = std::move (o); }
Handle& operator= (Handle&& o)
{
reset ();
std::swap (handle, o.handle);
return *this;
}
Handle (const Handle& o) { *this = o; }
Handle& operator= (const Handle& o)
{
reset ();
if (o.handle)
{
handle = RetainProc (o.handle);
}
return *this;
}
void assign (Type h)
{
reset ();
handle = h;
}
void reset ()
{
if (handle)
{
ReleaseProc (handle);
handle = nullptr;
}
}
operator Type () const { return handle; }
operator bool () const { return handle != nullptr; }
private:
Type handle {nullptr};
};
using ContextHandle = Handle<cairo_t*, decltype (&cairo_reference), cairo_reference,
decltype (&cairo_destroy), cairo_destroy>;
using SurfaceHandle =
Handle<cairo_surface_t*, decltype (&cairo_surface_reference), cairo_surface_reference,
decltype (&cairo_surface_destroy), cairo_surface_destroy>;
using PatternHandle =
Handle<cairo_pattern_t*, decltype (&cairo_pattern_reference), cairo_pattern_reference,
decltype (&cairo_pattern_destroy), cairo_pattern_destroy>;
using ScaledFontHandle = Handle<cairo_scaled_font_t*, decltype (&cairo_scaled_font_reference),
cairo_scaled_font_reference, decltype (&cairo_scaled_font_destroy),
cairo_scaled_font_destroy>;
//-----------------------------------------------------------------------------
} // Cairo
} // VSTGUI
@@ -0,0 +1,7 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../platform_x11.h"
@@ -0,0 +1,332 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cairobitmap.h"
#include "cairofont.h"
#include "cairogradient.h"
#include "cairographicscontext.h"
#include "x11frame.h"
#if VSTGUI_ENABLE_WAYLAND_SUPPORT
#include "waylandframe.h"
#include "waylandplatform.h"
#endif
#include "../iplatformframecallback.h"
#include "../common/fileresourceinputstream.h"
#include "../iplatformresourceinputstream.h"
#include "../iplatformgraphicsdevice.h"
#include "linuxstring.h"
#include "x11timer.h"
#include "x11fileselector.h"
#include "linuxtaskexecutor.h"
#include "linuxfactory.h"
#include <list>
#include <memory>
#include <chrono>
#include <X11/X.h>
#include <dlfcn.h>
#include <link.h>
struct wl_display;
struct xdg_surface;
struct xdg_toplevel;
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
struct LinuxFactory::Impl
{
std::string resPath;
std::unique_ptr<CairoGraphicsDeviceFactory> graphicsDeviceFactory {std::make_unique<CairoGraphicsDeviceFactory> ()};
PlatformTaskExecutorPtr taskExecutor {std::make_unique<LinuxTaskExecutor> ()};
SharedPointer<IRunLoop> runLoop {};
void setupResPath (void* handle)
{
if (handle && resPath.empty ())
{
struct link_map* map;
if (dlinfo (handle, RTLD_DI_LINKMAP, &map) == 0)
{
auto path = std::string (map->l_name);
for (int i = 0; i < 3; i++)
{
int delPos = path.find_last_of ('/');
if (delPos == -1)
{
fprintf (stderr, "Could not determine bundle location.\n");
return; // unexpected
}
path.erase (delPos, path.length () - delPos);
}
auto rp = realpath (path.data (), nullptr);
path = rp;
free (rp);
path += "/Contents/Resources/";
std::swap (resPath, path);
}
}
}
};
//-----------------------------------------------------------------------------
LinuxFactory::LinuxFactory (void* soHandle)
{
impl = std::unique_ptr<Impl> (new Impl);
impl->setupResPath (soHandle);
}
//-----------------------------------------------------------------------------
void LinuxFactory::finalize () noexcept { impl->taskExecutor->waitAllTasksExecuted (); }
//-----------------------------------------------------------------------------
void LinuxFactory::setResourcePath (const std::string& path) const noexcept
{
impl->resPath = path;
}
//-----------------------------------------------------------------------------
std::string LinuxFactory::getResourcePath () const noexcept
{
return impl->resPath;
}
//-----------------------------------------------------------------------------
void LinuxFactory::setScheduleMainQueueTaskFunc (
LinuxTaskExecutor::ScheduleMainQueueTaskFunc&& func) const noexcept
{
if (auto lte = dynamic_cast<LinuxTaskExecutor*> (impl->taskExecutor.get ()))
{
lte->setScheduleMainQueueTaskFunc (std::move (func));
}
else
{
vstgui_assert (false, "cannot set the func on a custom task executor");
}
}
//-----------------------------------------------------------------------------
void LinuxFactory::setRunLoop (const SharedPointer<IRunLoop>& runLoop) const noexcept
{
impl->runLoop = runLoop;
}
//-----------------------------------------------------------------------------
const SharedPointer<IRunLoop>& LinuxFactory::getRunLoop () const noexcept { return impl->runLoop; }
//-----------------------------------------------------------------------------
uint64_t LinuxFactory::getTicks () const noexcept
{
using namespace std::chrono;
return duration_cast<milliseconds> (steady_clock::now ().time_since_epoch ()).count ();
}
//-----------------------------------------------------------------------------
PlatformFramePtr LinuxFactory::createFrame (IPlatformFrameCallback* frame, const CRect& size,
void* parent, PlatformType parentType,
IPlatformFrameConfig* config) const noexcept
{
if (parentType == PlatformType::kDefaultNative || parentType == PlatformType::kX11EmbedWindowID)
{
auto x11Parent = reinterpret_cast<XID> (parent);
return makeOwned<X11::Frame> (frame, size, x11Parent, config);
}
#if VSTGUI_ENABLE_WAYLAND_SUPPORT
if (parentType == PlatformType::kWaylandSurfaceID)
{
// auto surface = reinterpret_cast<xdg_surface*> (parent);
return makeOwned<Wayland::Frame> (frame, size, config);
}
#endif
return nullptr;
}
//-----------------------------------------------------------------------------
PlatformFontPtr LinuxFactory::createFont (const UTF8String& name, const CCoord& size,
const int32_t& style) const noexcept
{
return makeOwned<Cairo::Font> (name, size, style);
}
//-----------------------------------------------------------------------------
bool LinuxFactory::getAllFontFamilies (const FontFamilyCallback& callback) const noexcept
{
return Cairo::Font::getAllFamilies (callback);
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr LinuxFactory::createBitmap (const CPoint& size) const noexcept
{
return makeOwned<Cairo::Bitmap> (size);
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr LinuxFactory::createBitmap (const CResourceDescription& desc) const noexcept
{
if (auto bitmap = makeOwned<Cairo::Bitmap> ())
{
if (bitmap->load (desc))
return bitmap;
}
return nullptr;
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr LinuxFactory::createBitmapFromPath (UTF8StringPtr absolutePath) const noexcept
{
return Cairo::Bitmap::create (absolutePath);
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr LinuxFactory::createBitmapFromMemory (const void* ptr,
uint32_t memSize) const noexcept
{
return Cairo::Bitmap::create (ptr, memSize);
}
//-----------------------------------------------------------------------------
PNGBitmapBuffer LinuxFactory::createBitmapMemoryPNGRepresentation (
const PlatformBitmapPtr& bitmap) const noexcept
{
if (auto cairoBitmap = dynamic_cast<Cairo::Bitmap*> (bitmap.get ()))
{
return cairoBitmap->createMemoryPNGRepresentation ();
}
return {};
}
//-----------------------------------------------------------------------------
PlatformResourceInputStreamPtr
LinuxFactory::createResourceInputStream (const CResourceDescription& desc) const noexcept
{
if (desc.type == CResourceDescription::kIntegerType)
return {};
auto path = impl->resPath;
path += desc.u.name;
return FileResourceInputStream::create (path);
}
//-----------------------------------------------------------------------------
PlatformStringPtr LinuxFactory::createString (UTF8StringPtr utf8String) const noexcept
{
return makeOwned<LinuxString> (utf8String);
}
//-----------------------------------------------------------------------------
PlatformTimerPtr LinuxFactory::createTimer (IPlatformTimerCallback* callback) const noexcept
{
#if VSTGUI_ENABLE_WAYLAND_SUPPORT
if (auto runLoop = Wayland::RunLoop::instance ().get ())
{
struct Timer : public IPlatformTimer,
public VSTGUI::ITimerHandler
{
Timer (IPlatformTimerCallback* callback) : callback (callback) {}
~Timer () noexcept { stop (); }
bool start (uint32_t periodMs) override
{
if (auto runLoop = Wayland::RunLoop::instance ().get ())
{
runLoop->registerTimer (periodMs, this);
return true;
}
return false;
}
bool stop () override
{
if (auto runLoop = Wayland::RunLoop::instance ().get ())
{
runLoop->unregisterTimer (this);
return true;
}
return false;
}
void onTimer () override { callback->fire (); }
IPlatformTimerCallback* callback;
};
auto timer = makeOwned<Timer> (callback);
return timer;
}
#endif
return makeOwned<X11::Timer> (callback);
}
//------------------------------------------------------------------------
bool LinuxFactory::setClipboard (const DataPackagePtr& data) const noexcept
{
// TODO: Linux Clipboard Implementation
return false;
}
//------------------------------------------------------------------------
auto LinuxFactory::getClipboard () const noexcept -> DataPackagePtr
{
// TODO: Linux Clipboard Implementation
return nullptr;
}
//-----------------------------------------------------------------------------
PlatformGradientPtr LinuxFactory::createGradient () const noexcept
{
return std::make_unique<Cairo::Gradient> ();
}
//-----------------------------------------------------------------------------
PlatformFileSelectorPtr LinuxFactory::createFileSelector (PlatformFileSelectorStyle style,
IPlatformFrame* frame) const noexcept
{
auto x11Frame = dynamic_cast<X11::Frame*> (frame);
return X11::createFileSelector (style, x11Frame);
}
//-----------------------------------------------------------------------------
const IPlatformGraphicsDeviceFactory& LinuxFactory::getGraphicsDeviceFactory () const noexcept
{
return *impl->graphicsDeviceFactory.get ();
}
//-----------------------------------------------------------------------------
const IPlatformTaskExecutor& LinuxFactory::getTaskExecutor () const noexcept
{
return *impl->taskExecutor;
}
//-----------------------------------------------------------------------------
bool LinuxFactory::replaceTaskExecutor (const ReplaceTaskExecFunc& replaceFunc) const noexcept
{
if (!replaceFunc)
return false;
impl->taskExecutor = replaceFunc (std::move (impl->taskExecutor));
return true;
}
//-----------------------------------------------------------------------------
const LinuxFactory* LinuxFactory::asLinuxFactory () const noexcept
{
return this;
}
//-----------------------------------------------------------------------------
const MacFactory* LinuxFactory::asMacFactory () const noexcept
{
return nullptr;
}
//-----------------------------------------------------------------------------
const Win32Factory* LinuxFactory::asWin32Factory () const noexcept
{
return nullptr;
}
//-----------------------------------------------------------------------------
CairoGraphicsDeviceFactory& LinuxFactory::getCairoGraphicsDeviceFactory () const noexcept
{
return *impl->graphicsDeviceFactory.get ();
}
//-----------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,155 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../platformfactory.h"
#include "../platform_linux.h"
#include "linuxtaskexecutor.h"
//-----------------------------------------------------------------------------
namespace VSTGUI {
class CairoGraphicsDeviceFactory;
//-----------------------------------------------------------------------------
class LinuxFactory final : public IPlatformFactory
{
public:
LinuxFactory (void* soHandle);
void setResourcePath (const std::string& path) const noexcept;
std::string getResourcePath () const noexcept;
void setScheduleMainQueueTaskFunc (
LinuxTaskExecutor::ScheduleMainQueueTaskFunc&& func) const noexcept;
void setRunLoop (const SharedPointer<IRunLoop>& runLoop) const noexcept;
const SharedPointer<IRunLoop>& getRunLoop () const noexcept;
void finalize () noexcept final;
/** Return platform ticks (millisecond resolution)
* @return ticks
*/
uint64_t getTicks () const noexcept final;
/** Create a platform frame object
* @param frame callback
* @param size size
* @param parent platform parent object
* @param parentType type of platform parent object
* @param config optional config object
* @return platform frame or nullptr on failure
*/
PlatformFramePtr createFrame (IPlatformFrameCallback* frame, const CRect& size, void* parent,
PlatformType parentType,
IPlatformFrameConfig* config = nullptr) const noexcept final;
/** Create a platform font object
* @param name name of the font
* @param size font size
* @param style font style
* @return platform font or nullptr on failure
*/
PlatformFontPtr createFont (const UTF8String& name, const CCoord& size,
const int32_t& style) const noexcept final;
/** Query all platform font families
* @param callback callback called for every font
* @return true on success
*/
bool getAllFontFamilies (const FontFamilyCallback& callback) const noexcept final;
/** Create an empty platform bitmap object
* @param size size of the bitmap
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmap (const CPoint& size) const noexcept final;
/** Create a platform bitmap object from a resource description
* @param desc description where to find the bitmap
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmap (const CResourceDescription& desc) const noexcept final;
/** Create a platform bitmap object from a file
* @param absolutePath the absolute path of the bitmap file location
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmapFromPath (UTF8StringPtr absolutePath) const noexcept final;
/** Create a platform bitmap object from memory
* @param ptr memory location
* @param memSize memory size
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmapFromMemory (const void* ptr,
uint32_t memSize) const noexcept final;
/** Create a memory representation of the platform bitmap in PNG format.
* @param bitmap the platform bitmap object
* @return memory buffer containing the PNG representation of the bitmap
*/
PNGBitmapBuffer
createBitmapMemoryPNGRepresentation (const PlatformBitmapPtr& bitmap) const noexcept final;
/** Create a platform resource input stream
* @param desc description where to find the file to open
* @return platform resource input stream or nullptr if not found
*/
PlatformResourceInputStreamPtr
createResourceInputStream (const CResourceDescription& desc) const noexcept final;
/** Create a platform string object
* @param utf8String optional initial UTF-8 encoded string
* @return platform string object or nullptr on failure
*/
PlatformStringPtr createString (UTF8StringPtr utf8String = nullptr) const noexcept final;
/** Create a platform timer object
* @param callback timer callback object
* @return platform timer object or nullptr on failure
*/
PlatformTimerPtr createTimer (IPlatformTimerCallback* callback) const noexcept final;
/** Set clipboard data
* @param data data to put on the clipboard
* @return true on success
*/
bool setClipboard (const DataPackagePtr& data) const noexcept final;
/** Get clipboard data
* @return data package pointer
*/
DataPackagePtr getClipboard () const noexcept final;
/** Create a platform gradient object
* @return platform gradient object or nullptr on failure
*/
PlatformGradientPtr createGradient () const noexcept final;
/** Create a platform file selector
* @param style file selector style
* @param frame frame
* @return platform file selector or nullptr on failure
*/
PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style,
IPlatformFrame* frame) const noexcept final;
/** Get the graphics device factory
*
* @return platform graphics device factory
*/
const IPlatformGraphicsDeviceFactory& getGraphicsDeviceFactory () const noexcept final;
const IPlatformTaskExecutor& getTaskExecutor () const noexcept final;
bool replaceTaskExecutor (const ReplaceTaskExecFunc& replaceFunc) const noexcept final;
const LinuxFactory* asLinuxFactory () const noexcept final;
const MacFactory* asMacFactory () const noexcept final;
const Win32Factory* asWin32Factory () const noexcept final;
CairoGraphicsDeviceFactory& getCairoGraphicsDeviceFactory () const noexcept;
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,20 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "linuxstring.h"
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
LinuxString::LinuxString (UTF8StringPtr utf8String) : str (utf8String) {}
//------------------------------------------------------------------------
void LinuxString::setUTF8String (UTF8StringPtr utf8String)
{
str = utf8String ? utf8String : "";
}
//------------------------------------------------------------------------
}
@@ -0,0 +1,26 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformstring.h"
#include <string>
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
class LinuxString : public IPlatformString
{
public:
LinuxString (UTF8StringPtr utf8String);
virtual void setUTF8String (UTF8StringPtr utf8String) override;
const std::string& get () const { return str; }
private:
std::string str;
};
//------------------------------------------------------------------------
}
@@ -0,0 +1,125 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "linuxtaskexecutor.h"
#include "../common/threadpooltaskexecutor.h"
#include "../../vstguidebug.h"
//------------------------------------------------------------------------
namespace VSTGUI {
struct LinuxTaskExecutor::Impl
{
using SerialQueueVector = std::vector<std::unique_ptr<Tasks::Detail::SerialQueue>>;
Tasks::Detail::ThreadPool threadPool {std::thread::hardware_concurrency ()};
uint64_t queueIdentifierCounter {};
SerialQueueVector serialQueues;
std::mutex serialQueueMutex;
ScheduleMainQueueTaskFunc scheduleMainQueueTaskFunc;
Tasks::Queue mainQueue {0u};
Tasks::Queue backgroundQueue {1u};
void waitAllTasksExecuted (SerialQueueVector::const_iterator it) const
{
while ((*it)->empty () == false)
std::this_thread::sleep_for (std::chrono::milliseconds (1));
}
SerialQueueVector::const_iterator findQueue (uint64_t identifier) const
{
return std::find_if (serialQueues.begin (), serialQueues.end (),
[&] (const auto& el) { return el->getIdentifier (); });
}
};
//------------------------------------------------------------------------
LinuxTaskExecutor::LinuxTaskExecutor () { impl = std::make_unique<Impl> (); }
//------------------------------------------------------------------------
LinuxTaskExecutor::~LinuxTaskExecutor () noexcept {}
//------------------------------------------------------------------------
void LinuxTaskExecutor::setScheduleMainQueueTaskFunc (ScheduleMainQueueTaskFunc&& func)
{
impl->scheduleMainQueueTaskFunc = std::move (func);
}
//------------------------------------------------------------------------
const Tasks::Queue& LinuxTaskExecutor::getMainQueue () const { return impl->mainQueue; }
//------------------------------------------------------------------------
const Tasks::Queue& LinuxTaskExecutor::getBackgroundQueue () const { return impl->backgroundQueue; }
//------------------------------------------------------------------------
Tasks::Queue LinuxTaskExecutor::makeSerialQueue (const char* name) const
{
std::lock_guard<std::mutex> lock (impl->serialQueueMutex);
impl->serialQueues.emplace_back (std::make_unique<Tasks::Detail::SerialQueue> (
impl->threadPool, ++impl->queueIdentifierCounter, name));
return {impl->queueIdentifierCounter};
}
void LinuxTaskExecutor::releaseSerialQueue (const Tasks::Queue& queue) const
{
std::lock_guard<std::mutex> lock (impl->serialQueueMutex);
auto it = impl->findQueue (queue.identifier);
if (it != impl->serialQueues.end ())
{
impl->waitAllTasksExecuted (it);
impl->serialQueues.erase (it);
}
}
//------------------------------------------------------------------------
void LinuxTaskExecutor::schedule (const Tasks::Queue& queue, Tasks::Task&& task) const
{
if (queue == getMainQueue ())
{
if (impl->scheduleMainQueueTaskFunc)
impl->scheduleMainQueueTaskFunc (std::move (task));
}
else if (queue == getBackgroundQueue ())
{
impl->threadPool.enqueue (std::move (task));
}
else
{
std::lock_guard<std::mutex> lock (impl->serialQueueMutex);
auto it = impl->findQueue (queue.identifier);
if (it != impl->serialQueues.end ())
(*it)->schedule (std::move (task));
}
}
//------------------------------------------------------------------------
void LinuxTaskExecutor::waitAllTasksExecuted (const Tasks::Queue& queue) const
{
if (queue == impl->backgroundQueue)
{
while (!impl->threadPool.empty ())
std::this_thread::sleep_for (std::chrono::milliseconds (1));
}
else
{
std::lock_guard<std::mutex> lock (impl->serialQueueMutex);
auto it = impl->findQueue (queue.identifier);
if (it != impl->serialQueues.end ())
impl->waitAllTasksExecuted (it);
}
}
//------------------------------------------------------------------------
void LinuxTaskExecutor::waitAllTasksExecuted () const
{
{
std::lock_guard<std::mutex> lock (impl->serialQueueMutex);
for (auto it = impl->serialQueues.begin (); it != impl->serialQueues.end (); ++it)
impl->waitAllTasksExecuted (it);
}
waitAllTasksExecuted (impl->backgroundQueue);
}
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,36 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformtaskexecutor.h"
#include <functional>
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
struct LinuxTaskExecutor final : IPlatformTaskExecutor
{
LinuxTaskExecutor ();
~LinuxTaskExecutor () noexcept override;
const Tasks::Queue& getMainQueue () const final;
const Tasks::Queue& getBackgroundQueue () const final;
Tasks::Queue makeSerialQueue (const char* name) const final;
void releaseSerialQueue (const Tasks::Queue& queue) const final;
void schedule (const Tasks::Queue& queue, Tasks::Task&& task) const final;
void waitAllTasksExecuted (const Tasks::Queue& queue) const final;
void waitAllTasksExecuted () const final;
using ScheduleMainQueueTaskFunc = std::function<void (Tasks::Task&&)>;
void setScheduleMainQueueTaskFunc (ScheduleMainQueueTaskFunc&& func);
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,479 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "waylandclientcontext.h"
#include <string>
#include <vector>
#include <algorithm>
#include <unordered_map>
// Wayland globals
#include "wayland-client-protocol.h"
#include "xdg-decoration-unstable-v1-client-protocol.h"
#include "xdg-shell-client-protocol.h"
#include "linux-dmabuf-v1-server-protocol.h"
#include "linux-dmabuf-v1-client-protocol.h"
using namespace WaylandServerDelegate;
namespace VSTGUI::Wayland {
//------------------------------------------------------------------------
using ContextListeners = std::vector<IContextListener*>;
using NamedWaylandOutput = std::pair<uint32_t, WaylandOutput>;
using WaylandOutputs = std::vector<NamedWaylandOutput>;
using StringType = std::string;
//------------------------------------------------------------------------
// WaylandGlobals
// https://wayland.freedesktop.org/docs/html/
// https://wayland-book.com/introduction.html
//------------------------------------------------------------------------
struct WaylandGlobals
{
using WlInterfaceName = std::string;
using Globals = std::unordered_map<uint32_t, WlInterfaceName>;
wl_compositor* compositor {nullptr};
wl_seat* seat {nullptr};
wl_shm* shm {nullptr};
wl_subcompositor* subcompositor {nullptr};
xdg_wm_base* wm_base {nullptr};
zwp_linux_dmabuf_v1* dmabuf {nullptr};
Globals objects; // Hold a map of all globals' interface names for better tracking!
};
//------------------------------------------------------------------------
static void handlePing (void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial)
{
auto globals = reinterpret_cast<WaylandGlobals*> (data);
xdg_wm_base_pong (globals->wm_base, serial);
}
//------------------------------------------------------------------------
static void addXdgWmBaseListener (WaylandGlobals& globals)
{
static const struct xdg_wm_base_listener xdgWmBaseListener = {
.ping = handlePing,
};
xdg_wm_base_add_listener (globals.wm_base, &xdgWmBaseListener, &globals);
}
//------------------------------------------------------------------------
static void handleGlobal (void* data, struct wl_registry* registry, uint32_t name, const char* interface,
uint32_t version)
{
printf ("interface: '%s', version: %d, name: %d\n", interface, version, name);
auto& globals = *reinterpret_cast<WaylandGlobals*> (data);
globals.objects.insert ({name, {interface}});
if (std::string_view (interface) == wl_compositor_interface.name)
{
static constexpr uint32_t kVersion = 6;
void* object =
wl_registry_bind (registry, name, &wl_compositor_interface, std::min (kVersion, version));
auto compositor = reinterpret_cast<wl_compositor*> (object);
globals.compositor = compositor;
return;
}
if (std::string_view (interface) == wl_subcompositor_interface.name)
{
static constexpr uint32_t kVersion = 1;
void* object =
wl_registry_bind (registry, name, &wl_subcompositor_interface, std::min (kVersion, version));
auto subcompositor = reinterpret_cast<wl_subcompositor*> (object);
globals.subcompositor = subcompositor;
return;
}
if (std::string_view (interface) == wl_shm_interface.name)
{
static constexpr uint32_t kVersion = 1;
void* object = wl_registry_bind (registry, name, &wl_shm_interface, std::min (kVersion, version));
auto shm = reinterpret_cast<wl_shm*> (object);
globals.shm = shm;
return;
}
if (std::string_view (interface) == xdg_wm_base_interface.name)
{
static constexpr uint32_t kVersion = 6;
void* object =
wl_registry_bind (registry, name, &xdg_wm_base_interface, std::min (kVersion, version));
auto wm_base = reinterpret_cast<xdg_wm_base*> (object);
globals.wm_base = wm_base;
return;
}
if (std::string_view (interface) == wl_seat_interface.name)
{
static constexpr uint32_t kVersion = 8;
void* object = wl_registry_bind (registry, name, &wl_seat_interface, std::min (kVersion, version));
auto seat = reinterpret_cast<wl_seat*> (object);
globals.seat = seat;
return;
}
if (std::string_view (interface) == wl_output_interface.name)
{
// We bind outputs in the WaylandClientContext
/*
static constexpr uint32_t kVersion = 3;
void* object =
wl_registry_bind (registry, name, &wl_output_interface, std::min (kVersion, version));
auto output = reinterpret_cast<wl_output*> (object);
return;
*/
}
if (std::string_view (interface) == zwp_linux_dmabuf_v1_interface.name)
{
static constexpr uint32_t kVersion = 4;
void* object = wl_registry_bind (
registry, name, &zwp_linux_dmabuf_v1_interface, std::min (kVersion, version));
auto dmabuf = reinterpret_cast<zwp_linux_dmabuf_v1*> (object);
globals.dmabuf = dmabuf;
return;
}
}
//------------------------------------------------------------------------
static void handleGlobalRemove (void* data, struct wl_registry* registry, uint32_t name)
{
auto globals = reinterpret_cast<WaylandGlobals*> (data);
const auto iter = globals->objects.find (name);
if (iter == globals->objects.end ())
return;
globals->objects.erase (iter);
}
//------------------------------------------------------------------------
static bool bindGlobals (wl_display* display, WaylandGlobals& globals)
{
if (!display)
return false;
static const struct wl_registry_listener registry_listener = {
.global = handleGlobal,
.global_remove = handleGlobalRemove,
};
auto registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, &registry_listener, &globals);
// Roundtrip to call the listener's callbacks.
wl_display_roundtrip (display);
addXdgWmBaseListener (globals);
return true;
}
//------------------------------------------------------------------------
// WaylandClientContext::Impl
//------------------------------------------------------------------------
struct WaylandClientContext::Impl
{
wl_display* display = nullptr;
WaylandGlobals globals;
WaylandOutputs outputs;
ContextListeners contextListeners;
uint32_t seatCapabilities = 0;
StringType seatName;
void setSeatCapabilities (int capabilities);
void notifyListeners (IContextListener::ChangeType changeType);
void addWaylandOutput (uint32_t name, wl_output* out);
void removeWaylandOutput (uint32_t name);
void addRegistryListener ();
void addSeatListener ();
void addOutputsListener ();
};
//------------------------------------------------------------------------
void WaylandClientContext::Impl::setSeatCapabilities (int capabilities)
{
seatCapabilities = capabilities;
// seatCapabilities &= ~(WL_SEAT_CAPABILITY_KEYBOARD);
notifyListeners (IContextListener::kSeatCapabilitiesChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::notifyListeners (IContextListener::ChangeType changeType)
{
for (auto& el : contextListeners)
el->contextChanged (changeType);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addWaylandOutput (uint32_t name, wl_output* output)
{
outputs.push_back ({name, {output}});
notifyListeners (IContextListener::kOutputsChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::removeWaylandOutput (uint32_t name)
{
auto iter = std::find_if (outputs.begin (), outputs.end (),
[name] (const auto& el) { return el.first == name; });
if (iter == outputs.end ())
return;
outputs.erase (iter);
notifyListeners (IContextListener::kOutputsChanged);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addRegistryListener ()
{
static const struct wl_registry_listener registry_listener = {
.global =
[] (void* data, struct wl_registry* registry, uint32_t name, const char* interface,
uint32_t version) {
// We only track outputs here for now
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
if (std::string_view (interface) == wl_output_interface.name)
{
static constexpr uint32_t kVersion = 3;
void* object =
wl_registry_bind (registry, name, &wl_output_interface, std::min (kVersion, version));
auto output = reinterpret_cast<wl_output*> (object);
self->addWaylandOutput (name, output);
return;
}
},
.global_remove =
[] (void* data, struct wl_registry* registry, uint32_t name) {
// We only track outputs here for now
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
self->removeWaylandOutput (name);
},
};
auto registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, &registry_listener, this);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addSeatListener ()
{
static const struct wl_seat_listener listener = {
.capabilities =
[] (void* data, struct wl_seat* wl_seat, uint32_t capabilities) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
self->setSeatCapabilities (capabilities);
},
.name =
[] (void* data, struct wl_seat* wl_seat, const char* name) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
if (name)
self->seatName = name;
}};
wl_seat_add_listener (globals.seat, &listener, this);
}
//------------------------------------------------------------------------
void WaylandClientContext::Impl::addOutputsListener ()
{
static const wl_output_listener listener = {
.geometry =
[] (void* data, wl_output* wl_output, int32_t x, int32_t y, int32_t physical_width,
int32_t physical_height, int32_t subpixel, const char* make, const char* model,
int32_t transform) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
WaylandOutput& output = iter->second;
output.handle = wl_output;
output.x = x;
output.y = y;
output.physicalWidth = physical_width;
output.physicalHeight = physical_height;
output.subPixelOrientation = subpixel;
output.transformType = transform;
const std::string_view makeStr {make, sizeof (output.manufacturer)};
std::copy (makeStr.begin (), makeStr.end (), output.manufacturer);
const std::string_view modelStr {model, sizeof (output.model)};
std::copy (modelStr.begin (), modelStr.end (), output.model);
}
},
.mode =
[] (void* data, wl_output* wl_output, uint32_t flags, int32_t width, int32_t height,
int32_t refresh) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
auto& output = iter->second;
output.width = width;
output.height = height;
output.refreshRate = refresh;
}
},
.done =
[] (void* data, wl_output* wl_output) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
self->notifyListeners (
IContextListener::kOutputsChanged);
},
.scale =
[] (void* data, wl_output* wl_output, int32_t factor) {
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
auto iter = std::find_if (
self->outputs.begin (), self->outputs.end (),
[wl_output] (const auto& el) { return el.second.handle == wl_output; });
if (iter != self->outputs.end ())
{
auto& output = iter->second;
output.scaleFactor = factor;
}
},
.name =
[] (void* data, wl_output* wl_output, const char* name) {
// Hmm, was never called.
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
},
.description =
[] (void* data, wl_output* wl_output, const char* description) {
// Hmm, was never called.
auto self = reinterpret_cast<WaylandClientContext::Impl*> (data);
},
};
for (auto& output : outputs)
wl_output_add_listener (output.second.handle, &listener, this);
}
//------------------------------------------------------------------------
// WaylandClientContext
//------------------------------------------------------------------------
WaylandClientContext::WaylandClientContext ()
{
impl = std::make_unique<Impl> ();
}
//------------------------------------------------------------------------
WaylandClientContext::~WaylandClientContext () {}
//------------------------------------------------------------------------
bool WaylandClientContext::initWayland (wl_display* display)
{
impl->display = display;
bool done = bindGlobals (display, impl->globals);
impl->addRegistryListener ();
impl->addSeatListener ();
// Initializes seat capabilities, outputs etc.
wl_display_roundtrip (impl->display);
impl->addOutputsListener ();
return done;
}
//------------------------------------------------------------------------
bool WaylandClientContext::addListener (IContextListener* listener)
{
if (!listener)
return false;
impl->contextListeners.push_back (listener);
return true;
}
//------------------------------------------------------------------------
bool WaylandClientContext::removeListener (IContextListener* listener)
{
const auto found = [listener] (const auto el) { return el == listener; };
auto iter = std::find_if (impl->contextListeners.begin (), impl->contextListeners.end (), found);
if (iter == impl->contextListeners.end ())
return false;
impl->contextListeners.erase (iter);
return true;
}
//------------------------------------------------------------------------
wl_compositor* WaylandClientContext::getCompositor () const
{
return impl->globals.compositor;
}
//------------------------------------------------------------------------
wl_subcompositor* WaylandClientContext::getSubCompositor () const
{
return impl->globals.subcompositor;
}
//------------------------------------------------------------------------
wl_shm* WaylandClientContext::getSharedMemory () const
{
return impl->globals.shm;
}
//------------------------------------------------------------------------
wl_seat* WaylandClientContext::getSeat () const
{
return impl->globals.seat;
}
//------------------------------------------------------------------------
xdg_wm_base* WaylandClientContext::getWindowManager () const
{
return impl->globals.wm_base;
}
//------------------------------------------------------------------------
uint32_t WaylandClientContext::getSeatCapabilities () const
{
return impl->seatCapabilities;
}
//------------------------------------------------------------------------
const char* WaylandClientContext::getSeatName () const
{
return impl->seatName.data ();
}
//------------------------------------------------------------------------
int WaylandClientContext::countOutputs () const
{
return impl->outputs.size ();
}
//------------------------------------------------------------------------
zwp_linux_dmabuf_v1* WaylandClientContext::getDmaBuffer () const
{
return impl->globals.dmabuf;
}
//------------------------------------------------------------------------
const WaylandOutput& WaylandClientContext::getOutput (int index) const
{
if (index < impl->outputs.size ())
return impl->outputs[index].second;
static WaylandOutput output {};
return output;
}
//------------------------------------------------------------------------
} // namespace VSTGUI::Wayland
@@ -0,0 +1,50 @@
//-----------------------------------------------------------------------------
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "iwaylandclientcontext.h"
#include <memory>
namespace VSTGUI::Wayland {
//------------------------------------------------------------------------
// WaylandClientContext
//------------------------------------------------------------------------
class WaylandClientContext final : public WaylandServerDelegate::IWaylandClientContext
{
public:
//------------------------------------------------------------------------
// WaylandServerDelegate
using WaylandOutput = WaylandServerDelegate::WaylandOutput;
using IContextListener = WaylandServerDelegate::IContextListener;
WaylandClientContext ();
~WaylandClientContext ();
bool initWayland (wl_display* display);
// IWaylandClientContext
bool addListener (IContextListener* listener) override;
bool removeListener (IContextListener* listener) override;
wl_compositor* getCompositor () const override;
wl_subcompositor* getSubCompositor () const override;
wl_shm* getSharedMemory () const override;
wl_seat* getSeat () const override;
xdg_wm_base* getWindowManager () const override;
uint32_t getSeatCapabilities () const override;
const char* getSeatName () const override;
int countOutputs () const override;
zwp_linux_dmabuf_v1* getDmaBuffer () const override;
const WaylandOutput& getOutput (int index) const override;
//------------------------------------------------------------------------
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // namespace VSTGUI::Wayland
@@ -0,0 +1,393 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
// Originally written and contributed to VSTGUI by PreSonus Software Ltd.
#include "waylandframe.h"
// #include "waylanddragging.h"
#include "waylandutils.h"
#include "../../cbuttonstate.h"
#include "../../cframe.h"
#include "../../crect.h"
#include "../../dragging.h"
#include "../../vstkeycode.h"
#include "../../cinvalidrectlist.h"
#include "../iplatformopenglview.h"
#include "../iplatformviewlayer.h"
#include "../iplatformtextedit.h"
#include "../iplatformoptionmenu.h"
#include "../common/fileresourceinputstream.h"
#include "../common/generictextedit.h"
#include "../common/genericoptionmenu.h"
#include "cairobitmap.h"
#include "linuxfactory.h"
#include "cairographicscontext.h"
#include "waylandplatform.h"
#include <cassert>
#include <iostream>
#include <unordered_map>
#include <cairo/cairo.h>
#ifdef None
#undef None
#endif
#include "../../events.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Wayland {
//------------------------------------------------------------------------
struct RedrawTimerHandler : ITimerHandler,
NonAtomicReferenceCounted
{
using RedrawCallback = std::function<void ()>;
RedrawTimerHandler (uint64_t delay, RedrawCallback&& redrawCallback)
: redrawCallback (std::move (redrawCallback))
{
RunLoop::instance ().get ()->registerTimer (delay, this);
}
~RedrawTimerHandler () noexcept { RunLoop::instance ().get ()->unregisterTimer (this); }
void onTimer () override
{
SharedPointer<RedrawTimerHandler> Self (this);
Self->redrawCallback ();
}
RedrawCallback redrawCallback;
};
//------------------------------------------------------------------------
struct DrawHandler
{
DrawHandler (ChildWindow& window)
: childWindow (window), windowSurface (nullptr), device (nullptr)
{
onSizeChanged (window.getSize ());
}
void onSizeChanged (const CPoint& size)
{
drawContext = nullptr;
device.reset ();
windowSurface.reset ();
void* buffer = childWindow.getBuffer ();
if (buffer == nullptr)
return;
auto s = cairo_image_surface_create_for_data (
static_cast<unsigned char*> (buffer), CAIRO_FORMAT_ARGB32, childWindow.getSize ().x,
childWindow.getSize ().y, childWindow.getBufferStride ());
if (cairo_surface_status (s) != CAIRO_STATUS_SUCCESS)
return;
windowSurface.assign (s);
device =
getPlatformFactory ().asLinuxFactory ()->getCairoGraphicsDeviceFactory ().addDevice (
cairo_surface_get_device (s));
auto cairoDevice = std::static_pointer_cast<CairoGraphicsDevice> (device);
drawContext = std::make_shared<CairoGraphicsDeviceContext> (*cairoDevice, windowSurface);
}
bool draw (const CInvalidRectList& dirtyRects, IPlatformFrameCallback* frame)
{
if (drawContext == nullptr)
onSizeChanged (childWindow.getSize ());
if (drawContext == nullptr)
return false;
CRect copyRect;
drawContext->beginDraw ();
frame->platformDrawRects (drawContext, 1, dirtyRects.data ());
for (auto rect : dirtyRects)
{
if (copyRect.isEmpty ())
copyRect = rect;
else
copyRect.unite (rect);
}
drawContext->endDraw ();
cairo_surface_flush (windowSurface);
childWindow.commit (copyRect);
return true;
}
private:
ChildWindow& childWindow;
Cairo::SurfaceHandle windowSurface;
PlatformGraphicsDevicePtr device;
std::shared_ptr<CairoGraphicsDeviceContext> drawContext;
};
//------------------------------------------------------------------------
struct Frame::Impl
{
using RectList = CInvalidRectList;
ChildWindow window;
DrawHandler drawHandler;
// TODO: DoubleClickDetector doubleClickDetector;
IPlatformFrameCallback* frame;
std::unique_ptr<GenericOptionMenuTheme> genericOptionMenuTheme;
SharedPointer<RedrawTimerHandler> redrawTimer;
RectList dirtyRects;
CCursorType currentCursor {kCursorDefault};
uint32_t pointerGrabed {0};
// TODO: WaylandDragAndDropHandler dndHandler;
//------------------------------------------------------------------------
Impl (IWaylandFrame* waylandFrame, CPoint size, IPlatformFrameCallback* frame)
: window (waylandFrame, size)
, drawHandler (window)
, frame (frame) //, dndHandler (&window, frame)
{
window.setFrame (frame);
}
//------------------------------------------------------------------------
~Impl () noexcept {}
//------------------------------------------------------------------------
void setSize (const CRect& size)
{
window.setSize (size);
drawHandler.onSizeChanged (size.getSize ());
dirtyRects.clear ();
dirtyRects.add (size);
}
//------------------------------------------------------------------------
void setCursor (CCursorType cursor)
{
if (currentCursor == cursor)
return;
currentCursor = cursor;
setCursorInternal (cursor);
}
//------------------------------------------------------------------------
void setCursorInternal (CCursorType cursor)
{
// TODO
}
//------------------------------------------------------------------------
void redraw ()
{
if (drawHandler.draw (dirtyRects, frame))
dirtyRects.clear ();
}
//------------------------------------------------------------------------
void invalidRect (CRect r)
{
dirtyRects.add (r);
if (redrawTimer)
return;
redrawTimer = makeOwned<RedrawTimerHandler> (16, [this] () {
if (dirtyRects.data ().empty ())
return;
redraw ();
});
}
};
//------------------------------------------------------------------------
Frame::Frame (IPlatformFrameCallback* frame, const CRect& size, IPlatformFrameConfig* config)
: IPlatformFrame (frame)
{
auto cfg = dynamic_cast<FrameConfig*> (config);
if (cfg && cfg->runLoop)
{
if (auto f = getPlatformFactory ().asLinuxFactory ())
{
if (f->getRunLoop () == nullptr)
f->setRunLoop (cfg->runLoop);
}
RunLoop::init (cfg->waylandHost);
}
impl = std::unique_ptr<Impl> (
new Impl (cfg ? cfg->waylandFrame : nullptr, {size.getWidth (), size.getHeight ()}, frame));
frame->platformOnActivate (true);
}
//------------------------------------------------------------------------
Frame::~Frame ()
{
impl.reset ();
RunLoop::exit ();
}
//------------------------------------------------------------------------
void Frame::optionMenuPopupStarted () {}
//------------------------------------------------------------------------
void Frame::optionMenuPopupStopped () {}
//------------------------------------------------------------------------
bool Frame::getGlobalPosition (CPoint& pos) const { return false; }
//------------------------------------------------------------------------
bool Frame::setSize (const CRect& newSize)
{
vstgui_assert (impl);
impl->setSize (newSize);
return true;
}
//------------------------------------------------------------------------
bool Frame::getSize (CRect& size) const
{
size.setSize (impl->window.getSize ());
return true;
}
//------------------------------------------------------------------------
bool Frame::getCurrentMousePosition (CPoint& mousePosition) const
{
// TODO
return false;
}
//------------------------------------------------------------------------
bool Frame::getCurrentMouseButtons (CButtonState& buttons) const
{
// TODO
return false;
}
//------------------------------------------------------------------------
bool Frame::getCurrentModifiers (Modifiers& modifiers) const
{
// TODO
return false;
}
//------------------------------------------------------------------------
bool Frame::setMouseCursor (CCursorType type)
{
impl->setCursor (type);
return true;
}
//------------------------------------------------------------------------
bool Frame::invalidRect (const CRect& rect)
{
impl->invalidRect (rect);
return true;
}
//------------------------------------------------------------------------
bool Frame::scrollRect (const CRect& src, const CPoint& distance)
{
(void)src;
(void)distance;
return false;
}
//------------------------------------------------------------------------
bool Frame::showTooltip (const CRect& rect, const char* utf8Text)
{
#warning TODO: Implementation
return false;
}
//------------------------------------------------------------------------
bool Frame::hideTooltip ()
{
#warning TODO: Implementation
return false;
}
//------------------------------------------------------------------------
void* Frame::getPlatformRepresentation () const
{
#warning TODO: Implementation
return nullptr;
// return reinterpret_cast<void*> (getX11WindowID ());
}
//------------------------------------------------------------------------
SharedPointer<IPlatformTextEdit> Frame::createPlatformTextEdit (IPlatformTextEditCallback* textEdit)
{
return makeOwned<GenericTextEdit> (textEdit);
}
//------------------------------------------------------------------------
SharedPointer<IPlatformOptionMenu> Frame::createPlatformOptionMenu ()
{
auto cFrame = dynamic_cast<CFrame*> (frame);
GenericOptionMenuTheme theme;
if (impl->genericOptionMenuTheme)
theme = *impl->genericOptionMenuTheme.get ();
auto optionMenu =
makeOwned<GenericOptionMenu> (cFrame, MouseEventButtonState (MouseButton::Left), theme);
optionMenu->setListener (this);
return optionMenu;
}
#if VSTGUI_OPENGL_SUPPORT
//------------------------------------------------------------------------
SharedPointer<IPlatformOpenGLView> Frame::createPlatformOpenGLView ()
{
#warning TODO: Implementation
return nullptr;
}
#endif
//------------------------------------------------------------------------
SharedPointer<IPlatformViewLayer> Frame::createPlatformViewLayer (
IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer)
{
// optional
return nullptr;
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
//------------------------------------------------------------------------
DragResult Frame::doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap)
{
return kDragError;
}
#endif
//------------------------------------------------------------------------
bool Frame::doDrag (const DragDescription& dragDescription,
const SharedPointer<IDragCallback>& callback)
{
return false;
}
//------------------------------------------------------------------------
PlatformType Frame::getPlatformType () const { return PlatformType::kWaylandSurfaceID; }
//------------------------------------------------------------------------
Optional<UTF8String> Frame::convertCurrentKeyEventToText ()
{
// TODO: return RunLoop::instance ().convertCurrentKeyEventToText ();
return Optional<UTF8String> ();
}
//------------------------------------------------------------------------
bool Frame::setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme)
{
if (theme)
impl->genericOptionMenuTheme =
std::unique_ptr<GenericOptionMenuTheme> (new GenericOptionMenuTheme (*theme));
else
impl->genericOptionMenuTheme = nullptr;
return true;
}
//------------------------------------------------------------------------
} // Wayland
} // VSTGUI
@@ -0,0 +1,70 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
// Originally written and contributed to VSTGUI by PreSonus Software Ltd.
#pragma once
#include "../../crect.h"
#include "../iplatformframe.h"
#include "../iplatformresourceinputstream.h"
#include "../platform_wayland.h"
#include "../common/genericoptionmenu.h"
#include <memory>
#include <functional>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Wayland {
//------------------------------------------------------------------------
class Frame : public IPlatformFrame,
public IGenericOptionMenuListener
{
public:
Frame (IPlatformFrameCallback* frame, const CRect& size, IPlatformFrameConfig* config);
~Frame ();
private:
bool getGlobalPosition (CPoint& pos) const override;
bool setSize (const CRect& newSize) override;
bool getSize (CRect& size) const override;
bool getCurrentMousePosition (CPoint& mousePosition) const override;
bool getCurrentMouseButtons (CButtonState& buttons) const override;
bool getCurrentModifiers (Modifiers& modifiers) const override;
bool setMouseCursor (CCursorType type) override;
bool invalidRect (const CRect& rect) override;
bool scrollRect (const CRect& src, const CPoint& distance) override;
bool showTooltip (const CRect& rect, const char* utf8Text) override;
bool hideTooltip () override;
void* getPlatformRepresentation () const override;
SharedPointer<IPlatformTextEdit>
createPlatformTextEdit (IPlatformTextEditCallback* textEdit) override;
SharedPointer<IPlatformOptionMenu> createPlatformOptionMenu () override;
#if VSTGUI_OPENGL_SUPPORT
SharedPointer<IPlatformOpenGLView> createPlatformOpenGLView () override;
#endif
SharedPointer<IPlatformViewLayer> createPlatformViewLayer (
IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer) override;
#if VSTGUI_ENABLE_DEPRECATED_METHODS
DragResult doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) override;
#endif
bool doDrag (const DragDescription& dragDescription,
const SharedPointer<IDragCallback>& callback) override;
PlatformType getPlatformType () const override;
void onFrameClosed () override {}
Optional<UTF8String> convertCurrentKeyEventToText () override;
bool setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme = nullptr) override;
void optionMenuPopupStarted () override;
void optionMenuPopupStopped () override;
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // Wayland
} // VSTGUI
@@ -0,0 +1,239 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
// Originally written and contributed to VSTGUI by PreSonus Software Ltd.
#include "waylandplatform.h"
#include "linuxfactory.h"
#include "../../cfileselector.h"
#include "../../cframe.h"
#include "../../cstring.h"
#include "../../events.h"
#include "waylandframe.h"
// #include "x11dragging.h"
#include "cairobitmap.h"
#include <cassert>
#include <chrono>
#include <array>
#include <dlfcn.h>
#include <iostream>
#include <locale>
#include <link.h>
#include <unordered_map>
#include <codecvt>
#include <xkbcommon/xkbcommon.h>
//#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#include "waylandclientcontext.h"
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
namespace Wayland {
//------------------------------------------------------------------------
namespace {
using VirtMap = std::unordered_map<xkb_keysym_t, VirtualKey>;
const VirtMap keyMap = {{XKB_KEY_BackSpace, VirtualKey::Back},
{XKB_KEY_Tab, VirtualKey::Tab},
{XKB_KEY_Clear, VirtualKey::Clear},
{XKB_KEY_Return, VirtualKey::Return},
{XKB_KEY_Pause, VirtualKey::Pause},
{XKB_KEY_Escape, VirtualKey::Escape},
{XKB_KEY_space, VirtualKey::Space},
{XKB_KEY_End, VirtualKey::End},
{XKB_KEY_Home, VirtualKey::Home},
{XKB_KEY_Left, VirtualKey::Left},
{XKB_KEY_Up, VirtualKey::Up},
{XKB_KEY_Right, VirtualKey::Right},
{XKB_KEY_Down, VirtualKey::Down},
{XKB_KEY_Page_Up, VirtualKey::PageUp},
{XKB_KEY_Page_Down, VirtualKey::PageDown},
{XKB_KEY_Select, VirtualKey::Select},
{XKB_KEY_Print, VirtualKey::Print},
{XKB_KEY_KP_Enter, VirtualKey::Enter},
{XKB_KEY_Insert, VirtualKey::Insert},
{XKB_KEY_Delete, VirtualKey::Delete},
{XKB_KEY_Help, VirtualKey::Help},
// Numpads ???
{XKB_KEY_KP_Multiply, VirtualKey::Multiply},
{XKB_KEY_KP_Add, VirtualKey::Add},
{XKB_KEY_KP_Separator, VirtualKey::Separator},
{XKB_KEY_KP_Subtract, VirtualKey::Subtract},
{XKB_KEY_KP_Decimal, VirtualKey::Decimal},
{XKB_KEY_KP_Divide, VirtualKey::Divide},
{XKB_KEY_F1, VirtualKey::F1},
{XKB_KEY_F2, VirtualKey::F2},
{XKB_KEY_F3, VirtualKey::F3},
{XKB_KEY_F4, VirtualKey::F4},
{XKB_KEY_F5, VirtualKey::F5},
{XKB_KEY_F6, VirtualKey::F6},
{XKB_KEY_F7, VirtualKey::F7},
{XKB_KEY_F8, VirtualKey::F8},
{XKB_KEY_F9, VirtualKey::F9},
{XKB_KEY_F10, VirtualKey::F10},
{XKB_KEY_F11, VirtualKey::F11},
{XKB_KEY_F12, VirtualKey::F12},
{XKB_KEY_Num_Lock, VirtualKey::NumLock},
{XKB_KEY_Scroll_Lock, VirtualKey::Scroll}, // correct ?
#if 0
{XKB_KEY_Shift_L, VirtualKey::SHIFT},
{XKB_KEY_Shift_R, VirtualKey::SHIFT},
{XKB_KEY_Control_L, VirtualKey::CONTROL},
{XKB_KEY_Control_R, VirtualKey::CONTROL},
{XKB_KEY_Alt_L, VirtualKey::ALT},
{XKB_KEY_Alt_R, VirtualKey::ALT},
#endif
{XKB_KEY_VoidSymbol, VirtualKey::None}};
const VirtMap shiftKeyMap = {{XKB_KEY_KP_Page_Up, VirtualKey::PageUp},
{XKB_KEY_KP_Page_Down, VirtualKey::PageDown},
{XKB_KEY_KP_Home, VirtualKey::Home},
{XKB_KEY_KP_End, VirtualKey::End}};
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
struct RunLoop::Impl : IEventHandler
{
SharedPointer<IWaylandHost> waylandHost;
std::atomic<uint32_t> useCount {0};
cairo_device_t* device {nullptr};
wl_display* display {nullptr};
WaylandClientContext clientContext;
bool inDispatch {false};
Impl ()
{
}
void init (const SharedPointer<IWaylandHost>& inWaylandHost)
{
if (++useCount != 1)
return;
waylandHost = inWaylandHost;
if (waylandHost == nullptr)
return;
display = waylandHost->openWaylandConnection ();
if (display == nullptr)
return;
clientContext.initWayland (display);
RunLoop::get ()->registerEventHandler (wl_display_get_fd (display), this);
flush ();
}
void exit ()
{
if (--useCount != 0)
return;
cairo_device_finish (device);
cairo_device_destroy (device);
device = nullptr;
RunLoop::get ()->unregisterEventHandler (this);
flush ();
if (waylandHost && display)
waylandHost->closeWaylandConnection (display);
waylandHost = nullptr;
display = nullptr;
}
void flush ()
{
if (display && !inDispatch)
wl_display_flush (display);
}
// IEventHandler
void onEvent () final
{
inDispatch = true;
if (wl_display_prepare_read (display) == 0)
wl_display_read_events (display);
wl_display_dispatch_pending (display);
inDispatch = false;
flush ();
}
};
//------------------------------------------------------------------------
RunLoop& RunLoop::instance ()
{
static RunLoop gInstance;
return gInstance;
}
//------------------------------------------------------------------------
void RunLoop::init (const SharedPointer<IWaylandHost>& waylandHost)
{
instance ().impl->init (waylandHost);
}
//------------------------------------------------------------------------
void RunLoop::exit () { instance ().impl->exit (); }
//------------------------------------------------------------------------
void RunLoop::flush () { instance ().impl->flush (); }
//------------------------------------------------------------------------
const SharedPointer<IRunLoop> RunLoop::get ()
{
return getPlatformFactory ().asLinuxFactory ()->getRunLoop ();
}
//------------------------------------------------------------------------
wl_display* RunLoop::getDisplay () { return instance ().impl->display; }
//------------------------------------------------------------------------
using namespace WaylandServerDelegate;
const IWaylandClientContext& RunLoop::getClientContext ()
{
return instance ().impl->clientContext;
}
//------------------------------------------------------------------------
bool RunLoop::hasPointerInput ()
{
return (instance ().impl->clientContext.getSeatCapabilities () & WL_SEAT_CAPABILITY_POINTER) != 0;
}
//------------------------------------------------------------------------
bool RunLoop::hasKeyboardInput ()
{
return (instance ().impl->clientContext.getSeatCapabilities () & WL_SEAT_CAPABILITY_KEYBOARD) != 0;
}
//------------------------------------------------------------------------
RunLoop::RunLoop () { impl = std::unique_ptr<Impl> (new Impl); }
//------------------------------------------------------------------------
RunLoop::~RunLoop () noexcept = default;
//------------------------------------------------------------------------
void RunLoop::setDevice (cairo_device_t* device)
{
if (impl->device != device)
{
cairo_device_destroy (impl->device);
impl->device = cairo_device_reference (device);
}
}
//------------------------------------------------------------------------
} // Wayland
} // VSTGUI
@@ -0,0 +1,61 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
// Originally written and contributed to VSTGUI by PreSonus Software Ltd.
#pragma once
#include "../../vstguifwd.h"
#include "waylandframe.h"
#include <atomic>
#include <memory>
#include <cairo/cairo.h>
struct xdg_wm_base;
struct wl_compositor;
struct wl_subcompositor;
struct wl_shm;
struct wl_seat;
namespace WaylandServerDelegate {
class IWaylandClientContext;
}
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
namespace Wayland {
class Frame;
class Timer;
//------------------------------------------------------------------------
struct RunLoop
{
using IWaylandClientContext = WaylandServerDelegate::IWaylandClientContext;
static void init (const SharedPointer<IWaylandHost>& waylandHost);
static void exit ();
static const SharedPointer<IRunLoop> get ();
static void flush ();
static wl_display* getDisplay ();
static const IWaylandClientContext& getClientContext ();
static bool hasPointerInput ();
static bool hasKeyboardInput ();
void setDevice (cairo_device_t* device);
static RunLoop& instance ();
private:
RunLoop ();
~RunLoop () noexcept;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // Wayland
} // VSTGUI
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,75 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
// Originally written and contributed to VSTGUI by PreSonus Software Ltd.
#pragma once
#include "waylandplatform.h"
#include "wayland-client-protocol.h"
#include "iwaylandclientcontext.h"
struct wl_subsurface;
struct wl_buffer;
struct wl_shm_pool;
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Wayland {
//------------------------------------------------------------------------
// ChildWindow
//------------------------------------------------------------------------
class ChildWindow : public wl_surface_listener
{
public:
ChildWindow (IWaylandFrame* waylandFrame, CPoint size);
~ChildWindow () noexcept;
void setFrame (IPlatformFrameCallback* frame);
IPlatformFrameCallback* getFrame () const;
void setSize (const CRect& rect);
const CPoint& getSize () const;
void* getBuffer () const;
int getBufferStride () const;
wl_surface* getSurface () const;
void commit (const CRect& rect);
//------------------------------------------------------------------------
private:
// wl_surface_listener
static void onEnter (void *data, wl_surface *wl_surface, wl_output *output);
static void onLeave (void *data, wl_surface *wl_surface, wl_output *output);
static void onPreferredBufferScale (void *data, wl_surface *wl_surface, int32_t factor);
static void onPreferredBufferTransform (void *data, wl_surface *wl_surface, uint32_t transform);
static void updateScaleFactor (ChildWindow* self, int32_t factor);
SharedPointer<IWaylandFrame> waylandFrame;
IPlatformFrameCallback* frameCallback;
bool initialized;
CPoint size;
void* data;
wl_surface* surface;
wl_subsurface* subSurface;
wl_buffer* buffer;
wl_shm_pool* pool;
int allocatedSize;
int byteSize;
int fd;
void initialize ();
void terminate ();
void createBuffer ();
void destroyBuffer ();
};
//------------------------------------------------------------------------
} // Wayland
} // VSTGUI
@@ -0,0 +1,569 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "x11dragging.h"
#include "x11utils.h"
#include <glib.h>
#include <cstdio>
#include <cassert>
#undef None
#if 0
#define DndTrace(fmt, ...) fprintf (stderr, "Dnd " fmt "\n", ## __VA_ARGS__);
#else
#define DndTrace(fmt, ...)
#endif
/*
Notes:
This is implemented according to the XDND specification, version 5. (1)
The Xdnd receiver:
* receive the `XdndEnter` message
* extract the types present in the dragged data
- identify a matching mimetype for either path, text, or binary
* receive the initial `XdndPosition` message
- request the X server to retrieve the data for the matched type
- identify this data with our proprietary atom `XVSTGUISelection`
- cache this message to process it when ready, in the next step
* receive a `SelectionNotify` message for our desired type
- data is ready, extract it
- finish building the IDataPackage, call `OnDragEnter`
- send the XdndStatus message, with the desired drag operation
* receive additional `XdndPosition` messages
- call `OnDragMove`
- send the `XdndStatus` message, with the desired drag operation
* either:
* receive the `XdndLeave` message
- call `OnDragLeave`
- clean up
* receive the `XdndDrop` message
- call `OnDrop` if data is accepted, otherwise `OnDragLeave`
- send `XdndFinished`
- clean up
Remark:
If the receiver is proxied by another window, the replies of type
`XdndStatus` and `XdndFinished` should also be directed to the proxy.
This is not supposed to be necessary, but it fixes GTK2 hosts.
This GTK2 problem will likely not be fixed. (2)
References
(1) the XDND specification
https://freedesktop.org/wiki/Specifications/XDND/
(2) GTK does not forward XDND protocol messages for X11 embedded windows
https://gitlab.gnome.org/GNOME/gtk/-/issues/2329
*/
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
uint32_t XdndDataPackage::getCount () const
{
return packageData.size ();
}
uint32_t XdndDataPackage::getDataSize (uint32_t index) const
{
if (index >= packageData.size ())
return 0;
return packageData[index].size ();
}
IDataPackage::Type XdndDataPackage::getDataType (uint32_t index) const
{
if (index >= packageData.size ())
return Type::kError;
return packageType;
}
uint32_t XdndDataPackage::getData (uint32_t index, const void*& buffer, Type& type) const
{
if (index >= packageData.size ())
{
buffer = nullptr;
type = Type::kError;
return 0;
}
buffer = packageData[index].data ();
type = packageType;
return packageData[index].size ();
}
//------------------------------------------------------------------------
XdndHandler::XdndHandler (ChildWindow* window, IPlatformFrameCallback* frame)
: window (window), frame (frame)
{
}
void XdndHandler::enter (xcb_client_message_event_t& event, xcb_window_t targetId)
{
clearState ();
unsigned protocolVersion = event.data.data32[1] >> 24;
if (protocolVersion < 5)
return;
DndTrace ("[recv] Enter window=%08X, source=%08X",
event.window,
event.data.data32[0]);
if (!Atoms::xDndSelection.valid () || !Atoms::xVstguiSelection.valid ())
return;
std::vector<xcb_atom_t> typeList = getTypeList (event);
IDataPackage::Type packageType = IDataPackage::Type::kError;
if (dndType == XCB_ATOM_NONE)
{
dndType = findFilePathType(typeList);
if (dndType != XCB_ATOM_NONE)
packageType = IDataPackage::kFilePath;
}
if (dndType == XCB_ATOM_NONE)
{
dndType = findTextType(typeList);
if (dndType != XCB_ATOM_NONE)
packageType = IDataPackage::kText;
}
if (dndType == XCB_ATOM_NONE)
{
dndType = findBinaryType(typeList);
if (dndType != XCB_ATOM_NONE)
packageType = IDataPackage::kBinary;
}
if (packageType != IDataPackage::Type::kError)
{
package = makeOwned<XdndDataPackage> ();
package->setPackageType (packageType);
state = State::DragInitiated;
dndTarget = targetId;
dndSource = event.data.data32[0];
}
}
void XdndHandler::position (xcb_client_message_event_t& event)
{
DndTrace ("[recv] Position window=%08X, source=%08X, x=%d, y=%d, action=%s",
event.window,
event.data.data32[0],
event.data.data32[2] >> 16, event.data.data32[2] & 0xffff,
getAtomName (event.data.data32[4]).c_str ());
if (event.data.data32[0] != dndSource)
return;
switch (state)
{
case State::DragInitiated:
{
dndPosition = Optional<xcb_client_message_event_t> (event);
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_delete_property (
xcb, window->getID (), Atoms::xVstguiSelection ());
xcb_convert_selection (
xcb, window->getID (), Atoms::xDndSelection (), dndType,
Atoms::xVstguiSelection (), dndPosition->data.data32[3]);
}
break;
case State::DragEntering:
dragOperation = frame->platformOnDragEnter (getEventData ());
state = State::DragMoving;
replyStatus ();
break;
case State::DragMoving:
dragOperation = frame->platformOnDragMove (getEventData ());
replyStatus ();
break;
default:
break;
}
}
void XdndHandler::leave (xcb_client_message_event_t& event)
{
DndTrace ("[recv] Leave window=%08X, source=%08X",
event.window,
event.data.data32[0]);
if (event.data.data32[0] != dndSource)
return;
if (dndPosition)
{
frame->platformOnDragLeave (getEventData ());
}
clearState ();
}
void XdndHandler::drop (xcb_client_message_event_t& event)
{
DndTrace ("[recv] Drop window=%08X, source=%08X",
event.window,
event.data.data32[0]);
if (event.data.data32[0] != dndSource)
return;
if (dndPosition)
{
if (dragOperation != DragOperation::None)
frame->platformOnDrop (getEventData ());
else
frame->platformOnDragLeave (getEventData ());
replyFinished ();
}
clearState ();
}
void XdndHandler::selectionNotify (xcb_selection_notify_event_t& event)
{
if (state == State::DragInitiated &&
event.requestor == window->getID () && event.target == dndType &&
Atoms::xDndSelection.valid () && Atoms::xVstguiSelection.valid () &&
event.selection == Atoms::xDndSelection () &&
event.property == Atoms::xVstguiSelection ())
{
auto xcb = RunLoop::instance ().getXcbConnection ();
auto cookie = xcb_get_property (
xcb, true, window->getID (), Atoms::xVstguiSelection (),
XCB_GET_PROPERTY_TYPE_ANY, 0, 4096);
std::vector<std::string> packageData;
auto reply = xcb_get_property_reply (xcb, cookie, nullptr);
if (reply)
{
std::string data (
reinterpret_cast<char*> (xcb_get_property_value (reply)),
xcb_get_property_value_length (reply));
if (Atoms::xMimeTypeUriList.valid () &&
dndType == Atoms::xMimeTypeUriList ())
{
extractFilePathsFromUriList (data, packageData);
}
else
{
packageData.resize (1);
packageData[0] = std::move (data);
}
free (reply);
}
if (packageData.empty ())
clearState ();
else
{
package->setPackageData (std::move (packageData));
state = State::DragEntering;
if (dndPosition)
position (*dndPosition);
}
}
}
void XdndHandler::clearState ()
{
state = State::DragClear;
dndTarget = 0;
dndSource = 0;
dndType = XCB_ATOM_NONE;
dndPosition.reset ();
package = SharedPointer<XdndDataPackage> ();
dragOperation = DragOperation::None;
}
DragEventData XdndHandler::getEventData () const
{
assert (package);
DragEventData eventData;
eventData.drag = package.get ();
eventData.pos = getEventPosition ();
// TODO: the modifiers
return eventData;
}
CPoint XdndHandler::getEventPosition () const
{
assert (dndPosition);
xcb_client_message_event_t event = *dndPosition;
int x = event.data.data32[2] >> 16;
int y = event.data.data32[2] & 0xffff;
auto xcb = RunLoop::instance ().getXcbConnection ();
auto setup = xcb_get_setup (xcb);
auto iter = xcb_setup_roots_iterator (setup);
auto screen = iter.data;
auto cookie = xcb_translate_coordinates (
xcb, screen->root, window->getID (), x, y);
auto reply = xcb_translate_coordinates_reply (xcb, cookie, nullptr);
if (reply)
{
x = reply->dst_x;
y = reply->dst_y;
free (reply);
}
return CPoint (x, y);
}
void XdndHandler::replyStatus ()
{
if (!Atoms::xDndStatus.valid ())
return;
bool accepted = dragOperation != DragOperation::None;
xcb_window_t dndSource = dndPosition->data.data32[0];
xcb_client_message_event_t event {};
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = dndSource;
event.type = Atoms::xDndStatus ();
event.data.data32[0] = dndTarget;
event.data.data32[1] = accepted;
switch (dragOperation)
{
case DragOperation::Copy:
if (Atoms::xDndActionCopy.valid ())
event.data.data32[4] = Atoms::xDndActionCopy ();
break;
case DragOperation::Move:
if (Atoms::xDndActionMove.valid ())
event.data.data32[4] = Atoms::xDndActionMove ();
break;
default:
break;
}
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_window_t receiver = getXdndProxy (dndSource);
if (receiver == 0)
receiver = dndSource;
DndTrace ("[send] Status receiver=%08X, window=%08X, target=%08X, accept=%d, x=%d, y=%d, w=%d, h=%d, action=%s",
receiver,
event.window,
event.data.data32[0],
event.data.data32[1] & 1,
event.data.data32[2] >> 16, event.data.data32[2] & 0xffff,
event.data.data32[3] >> 16, event.data.data32[3] & 0xffff,
event.data.data32[4] ? getAtomName (event.data.data32[4]).c_str () : "None");
xcb_send_event (
xcb, false, receiver, XCB_EVENT_MASK_NO_EVENT,
reinterpret_cast<const char*> (&event));
}
void XdndHandler::replyFinished ()
{
if (!Atoms::xDndFinished.valid ())
return;
bool accepted = dragOperation != DragOperation::None;
xcb_window_t dndSource = dndPosition->data.data32[0];
xcb_client_message_event_t event {};
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.window = dndSource;
event.type = Atoms::xDndFinished ();
event.data.data32[0] = dndTarget;
event.data.data32[1] = accepted;
switch (dragOperation)
{
case DragOperation::Copy:
if (Atoms::xDndActionCopy.valid ())
event.data.data32[2] = Atoms::xDndActionCopy ();
break;
case DragOperation::Move:
if (Atoms::xDndActionMove.valid ())
event.data.data32[2] = Atoms::xDndActionMove ();
break;
default:
break;
}
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_window_t receiver = getXdndProxy (dndSource);
if (receiver == 0)
receiver = dndSource;
DndTrace ("[send] Finished receiver=%08X, window=%08X, target=%08X, accept=%d, action=%s",
receiver,
event.window,
event.data.data32[0],
event.data.data32[1] & 1,
event.data.data32[2] ? getAtomName (event.data.data32[2]).c_str () : "None");
xcb_send_event (
xcb, false, receiver, XCB_EVENT_MASK_NO_EVENT,
reinterpret_cast<const char*> (&event));
}
XdndHandler::TypeList XdndHandler::getTypeList (xcb_client_message_event_t& event)
{
TypeList typeList;
typeList.reserve (32);
xcb_window_t sourceId = event.data.data32[0];
bool longTypeList = event.data.data32[1] & 1;
if (!longTypeList)
{
for (int i = 2; i < 5; ++i) {
uint32_t type = event.data.data32[i];
if (type != XCB_NONE)
typeList.push_back (type);
}
}
else if (Atoms::xDndTypeList.valid ())
{
auto xcb = RunLoop::instance ().getXcbConnection ();
auto cookie = xcb_get_property (
xcb, false, sourceId, Atoms::xDndTypeList (), XCB_ATOM_ATOM,
0, typeList.capacity ());
auto reply = xcb_get_property_reply (xcb, cookie, nullptr);
if (reply)
{
int length = xcb_get_property_value_length (reply) / 4;
const uint32_t* data = static_cast<uint32_t*> (xcb_get_property_value (reply));
for (int i = 0; i < length; ++i)
typeList.push_back (data[i]);
free (reply);
}
}
return typeList;
}
xcb_atom_t XdndHandler::findFilePathType (const TypeList& typeList)
{
xcb_atom_t type = XCB_ATOM_NONE;
if (type == XCB_ATOM_NONE)
type = searchType (typeList, Atoms::xMimeTypeUriList);
return type;
}
xcb_atom_t XdndHandler::findTextType (const TypeList& typeList)
{
xcb_atom_t type = XCB_ATOM_NONE;
if (type == XCB_ATOM_NONE)
type = searchType (typeList, Atoms::xMimeTypeTextPlainUtf8);
if (type == XCB_ATOM_NONE)
type = searchType (typeList, Atoms::xMimeTypeTextPlain);
return type;
}
xcb_atom_t XdndHandler::findBinaryType (const TypeList& typeList)
{
xcb_atom_t type = XCB_ATOM_NONE;
if (type == XCB_ATOM_NONE)
type = searchType (typeList, Atoms::xMimeTypeApplicationOctetStream);
return type;
}
xcb_atom_t XdndHandler::searchType (const TypeList& typeList, const Atom& atom)
{
if (typeList.empty () || !atom.valid ())
return XCB_ATOM_NONE;
xcb_atom_t needle = atom ();
for (xcb_atom_t type : typeList) {
if (type == needle)
return type;
}
return XCB_ATOM_NONE;
}
void XdndHandler::extractFilePathsFromUriList (const std::string& data, std::vector<std::string>& filePaths)
{
filePaths.clear ();
filePaths.reserve (8);
char** uriList = g_uri_list_extract_uris (data.c_str ());
if (!uriList)
return;
for (char** uriPtr = uriList; *uriPtr; ++uriPtr)
{
char* uriHostname = nullptr;
char* uriFilename = g_filename_from_uri (*uriPtr, &uriHostname, nullptr);
if (uriFilename)
{
if (!uriHostname)
filePaths.push_back (uriFilename);
g_free (uriFilename);
g_free (uriHostname);
}
}
g_strfreev (uriList);
}
//------------------------------------------------------------------------
bool isXdndClientMessage (const xcb_client_message_event_t& event)
{
if ((event.response_type & ~0x80) != XCB_CLIENT_MESSAGE)
return false;
const std::string name = getAtomName (event.type);
return name.size () >= 4 && !memcmp (name.data (), "Xdnd", 4);
}
xcb_window_t getXdndProxy (xcb_window_t windowId)
{
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_window_t proxyId = 0;
xcb_get_property_cookie_t cookie = xcb_get_property (
xcb, false, windowId, Atoms::xDndProxy (),
XCB_ATOM_WINDOW, 0, 1);
xcb_get_property_reply_t *reply = xcb_get_property_reply (
xcb, cookie, nullptr);
if (reply) {
if (xcb_get_property_value_length (reply) == 4)
proxyId = *static_cast<uint32_t*> (xcb_get_property_value (reply));
free (reply);
}
return proxyId;
}
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,87 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../vstguifwd.h"
#include "../../dragging.h"
#include "../../optional.h"
#include <xcb/xcb.h>
#include <vector>
#undef None
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
struct Atom;
struct ChildWindow;
//------------------------------------------------------------------------
class XdndDataPackage : public IDataPackage
{
public:
uint32_t getCount () const override;
uint32_t getDataSize (uint32_t index) const override;
Type getDataType (uint32_t index) const override;
uint32_t getData (uint32_t index, const void*& buffer, Type& type) const override;
void setPackageType (Type t) { packageType = t; }
void setPackageData (std::vector<std::string>&& d) { packageData = std::move(d); }
private:
Type packageType = Type::kError;
std::vector<std::string> packageData;
};
//------------------------------------------------------------------------
class XdndHandler
{
public:
XdndHandler (ChildWindow* window, IPlatformFrameCallback* frame);
void enter (xcb_client_message_event_t& event, xcb_window_t targetId);
void position (xcb_client_message_event_t& event);
void leave (xcb_client_message_event_t& event);
void drop (xcb_client_message_event_t& event);
void selectionNotify (xcb_selection_notify_event_t& event);
private:
enum class State {
DragClear,
DragInitiated,
DragEntering,
DragMoving,
};
ChildWindow* window = nullptr;
IPlatformFrameCallback* frame = nullptr;
State state = State::DragClear;
xcb_window_t dndTarget = 0;
xcb_window_t dndSource = 0;
xcb_atom_t dndType = XCB_ATOM_NONE;
Optional<xcb_client_message_event_t> dndPosition;
SharedPointer<XdndDataPackage> package;
DragOperation dragOperation = DragOperation::None;
void clearState ();
DragEventData getEventData () const;
CPoint getEventPosition () const;
void replyStatus ();
void replyFinished ();
typedef std::vector<xcb_atom_t> TypeList;
static TypeList getTypeList (xcb_client_message_event_t& event);
static xcb_atom_t findFilePathType (const TypeList& typeList);
static xcb_atom_t findTextType (const TypeList& typeList);
static xcb_atom_t findBinaryType (const TypeList& typeList);
static xcb_atom_t searchType (const TypeList& typeList, const Atom& atom);
static void extractFilePathsFromUriList (const std::string& data, std::vector<std::string>& filePaths);
};
bool isXdndClientMessage (const xcb_client_message_event_t& event);
xcb_window_t getXdndProxy (xcb_window_t windowId);
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,260 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "x11fileselector.h"
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <string>
#include <vector>
#include <memory>
#include <cerrno>
#include <cassert>
extern "C" { extern char **environ; }
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
static constexpr auto kdialogpath = "/usr/bin/kdialog";
static constexpr auto zenitypath = "/usr/bin/zenity";
//------------------------------------------------------------------------
struct FileSelector : IPlatformFileSelector
{
FileSelector (PlatformFileSelectorStyle style) : style (style) { identifiyExDialogType (); }
~FileSelector () noexcept { closeProcess (); }
bool cancel () override
{
closeProcess ();
return false;
}
bool runDialog (const PlatformFileSelectorConfig& config)
{
switch (exDialogType)
{
case ExDialogType::kdialog:
return runKDialog (config);
case ExDialogType::zenity:
return runZenity (config);
case ExDialogType::none:
break;
}
return false;
}
bool run (const PlatformFileSelectorConfig& config) override
{
if (runDialog (config))
{
std::string path;
path.reserve (1024);
ssize_t count;
char buffer[1024];
while ((count = read (readerFd, buffer, sizeof (buffer))) > 0 ||
(count == -1 && errno == EINTR))
{
if (count > 0)
path.append (buffer, count);
}
std::vector<UTF8String> result;
if (count != -1)
{
if (! path.empty () && path[0] == '/')
{
if (path.back () == '\n')
path.pop_back ();
result.emplace_back (path);
}
}
if (config.doneCallback)
config.doneCallback (std::move (result));
return true;
}
return false;
}
private:
enum class ExDialogType
{
none,
kdialog,
zenity
};
void identifiyExDialogType ()
{
if (access (zenitypath, X_OK) != -1)
exDialogType = ExDialogType::zenity;
if (access (kdialogpath, X_OK) != -1)
exDialogType = ExDialogType::kdialog;
}
bool runKDialog (const PlatformFileSelectorConfig& config)
{
std::vector<std::string> args;
args.reserve (16);
args.push_back (kdialogpath);
if (style == PlatformFileSelectorStyle::SelectFile)
{
args.push_back ("--getopenfilename");
args.push_back ("--separate-output");
}
else if (style == PlatformFileSelectorStyle::SelectSaveFile)
args.push_back ("--getsavefilename");
else if (style == PlatformFileSelectorStyle::SelectDirectory)
args.push_back ("--getexistingdirectory");
if (hasBit (config.flags, PlatformFileSelectorFlags::MultiFileSelection))
args.push_back ("--multiple");
if (!config.title.empty ())
{
args.push_back ("--title");
args.push_back (config.title.getString ());
}
if (!config.initialPath.empty ())
args.push_back (config.initialPath.getString ());
if (startProcess (convertToArgv (args).data ()))
{
return true;
}
return false;
}
bool runZenity (const PlatformFileSelectorConfig& config)
{
std::vector<std::string> args;
args.reserve (16);
args.push_back (zenitypath);
args.push_back ("--file-selection");
if (style == PlatformFileSelectorStyle::SelectDirectory)
args.push_back ("--directory");
else if (style == PlatformFileSelectorStyle::SelectSaveFile)
{
args.push_back ("--save");
args.push_back ("--confirm-overwrite");
}
if (!config.title.empty ())
args.push_back ("--title=" + config.title.getString ());
if (!config.initialPath.empty ())
args.push_back ("--filename=" + config.initialPath.getString ());
if (startProcess (convertToArgv (args).data ()))
{
return true;
}
return false;
}
static std::vector<char*> convertToArgv (const std::vector<std::string>& args)
{
std::vector<char*> argv (args.size () + 1);
for (size_t i = 0, n = args.size (); i < n; ++i)
argv[i] = const_cast<char*>(args[i].c_str ());
return argv;
}
bool startProcess (char* argv[])
{
closeProcess ();
struct PipePair
{
int fd[2] = { -1, -1 };
~PipePair ()
{
if (fd[0] != -1) close (fd[0]);
if (fd[1] != -1) close (fd[1]);
}
};
PipePair rw;
if (pipe (rw.fd) != 0)
return false;
#if 0
char** envp = environ;
#else
std::vector<char*> cleanEnviron;
cleanEnviron.reserve (256);
for (char** envp = environ; *envp; ++envp)
{
// ensure the process will link with system libraries,
// and not these from the Ardour bundle.
if (strncmp (*envp, "LD_LIBRARY_PATH=", 16) == 0)
continue;
cleanEnviron.push_back (*envp);
}
cleanEnviron.push_back (nullptr);
char** envp = cleanEnviron.data ();
#endif
pid_t forkPid = vfork ();
if (forkPid == -1)
return false;
if (forkPid == 0) {
execute (argv, envp, rw.fd);
assert (false);
}
spawnPid = forkPid;
close (rw.fd[1]);
rw.fd[1] = -1;
readerFd = rw.fd[0];
rw.fd[0] = -1;
return true;
}
[[noreturn]]
static void execute (char* argv[], char* envp[], const int pipeFd[2])
{
close (pipeFd[0]);
if (dup2 (pipeFd[1], STDOUT_FILENO) == -1)
_exit (1);
close (pipeFd[1]);
execve (argv[0], argv, envp);
_exit (1);
}
void closeProcess ()
{
if (spawnPid != -1)
{
if (waitpid (spawnPid, nullptr, WNOHANG) == 0)
{
kill (spawnPid, SIGTERM);
waitpid (spawnPid, nullptr, 0);
}
spawnPid = -1;
}
if (readerFd != -1)
{
close (readerFd);
readerFd = -1;
}
}
PlatformFileSelectorStyle style;
ExDialogType exDialogType{ExDialogType::none};
pid_t spawnPid = -1;
int readerFd = -1;
};
//------------------------------------------------------------------------
PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style, Frame* frame)
{
return std::make_shared<FileSelector> (style);
}
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,19 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformfileselector.h"
#include "x11frame.h"
//-----------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
//-----------------------------------------------------------------------------
PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style, Frame* frame);
//-----------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,850 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "x11frame.h"
#include "x11dragging.h"
#include "x11utils.h"
#include "../../cbuttonstate.h"
#include "../../cframe.h"
#include "../../crect.h"
#include "../../dragging.h"
#include "../../vstkeycode.h"
#include "../../cinvalidrectlist.h"
#include "../iplatformopenglview.h"
#include "../iplatformviewlayer.h"
#include "../iplatformtextedit.h"
#include "../iplatformoptionmenu.h"
#include "../common/fileresourceinputstream.h"
#include "../common/generictextedit.h"
#include "../common/genericoptionmenu.h"
#include "cairobitmap.h"
#include "linuxfactory.h"
#include "cairographicscontext.h"
#include "x11platform.h"
#include "x11utils.h"
#include <cassert>
#include <iostream>
#include <unordered_map>
#include <X11/Xlib.h>
#include <xcb/xcb.h>
#include <xcb/xcb_util.h>
#include <cairo/cairo-xcb.h>
#ifdef None
#undef None
#endif
#include "../../events.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
namespace {
//------------------------------------------------------------------------
inline void setupMouseEventButtons (MouseEvent& event, xcb_button_t value)
{
switch (value)
{
case 1:
event.buttonState.add (MouseButton::Left);
break;
case 2:
event.buttonState.add (MouseButton::Middle);
break;
case 3:
event.buttonState.add (MouseButton::Right);
break;
}
}
//------------------------------------------------------------------------
inline void setupMouseEventButtons (MouseEvent& event, int state)
{
if (state & XCB_BUTTON_MASK_1)
event.buttonState.add (MouseButton::Left);
if (state & XCB_BUTTON_MASK_2)
event.buttonState.add (MouseButton::Right);
if (state & XCB_BUTTON_MASK_3)
event.buttonState.add (MouseButton::Middle);
}
//------------------------------------------------------------------------
inline void setupEventModifiers (Modifiers& modifiers, int state)
{
if (state & XCB_MOD_MASK_CONTROL)
modifiers.add (ModifierKey::Control);
if (state & XCB_MOD_MASK_SHIFT)
modifiers.add (ModifierKey::Shift);
if (state & (XCB_MOD_MASK_1 | XCB_MOD_MASK_5))
modifiers.add (ModifierKey::Alt);
}
//------------------------------------------------------------------------
inline CButtonState translateMouseButtons (xcb_button_t value)
{
switch (value)
{
case 1:
return kLButton;
case 2:
return kMButton;
case 3:
return kRButton;
}
return 0;
}
//------------------------------------------------------------------------
inline CButtonState translateMouseButtons (int state)
{
CButtonState buttons = 0;
if (state & XCB_BUTTON_MASK_1)
buttons |= kLButton;
if (state & XCB_BUTTON_MASK_2)
buttons |= kRButton;
if (state & XCB_BUTTON_MASK_3)
buttons |= kMButton;
return buttons;
}
//------------------------------------------------------------------------
inline uint32_t translateModifiers (int state)
{
uint32_t buttons = 0;
if (state & XCB_MOD_MASK_CONTROL)
buttons |= kControl;
if (state & XCB_MOD_MASK_SHIFT)
buttons |= kShift;
if (state & (XCB_MOD_MASK_1 | XCB_MOD_MASK_5))
buttons |= kAlt;
return buttons;
}
//------------------------------------------------------------------------
inline Modifiers toModifiers (int state)
{
Modifiers mods;
if (state & XCB_MOD_MASK_CONTROL)
mods.add (ModifierKey::Control);
if (state & XCB_MOD_MASK_SHIFT)
mods.add (ModifierKey::Shift);
if (state & (XCB_MOD_MASK_1 | XCB_MOD_MASK_5))
mods.add (ModifierKey::Alt);
if (state & XCB_MOD_MASK_4)
mods.add (ModifierKey::Super);
return mods;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
struct RedrawTimerHandler
: ITimerHandler
, NonAtomicReferenceCounted
{
using RedrawCallback = std::function<void ()>;
RedrawTimerHandler (uint64_t delay, RedrawCallback&& redrawCallback)
: redrawCallback (std::move (redrawCallback))
{
RunLoop::instance ().get ()->registerTimer (delay, this);
}
~RedrawTimerHandler () noexcept { RunLoop::instance ().get ()->unregisterTimer (this); }
void onTimer () override
{
SharedPointer<RedrawTimerHandler> Self (this);
Self->redrawCallback ();
}
RedrawCallback redrawCallback;
};
//------------------------------------------------------------------------
struct DrawHandler
{
DrawHandler (const ChildWindow& window)
{
auto s = cairo_xcb_surface_create (RunLoop::instance ().getXcbConnection (),
window.getID (), window.getVisual (),
window.getSize ().x, window.getSize ().y);
windowSurface.assign (s);
device =
getPlatformFactory ().asLinuxFactory ()->getCairoGraphicsDeviceFactory ().addDevice (
cairo_surface_get_device (s));
onSizeChanged (window.getSize ());
}
~DrawHandler ()
{
getPlatformFactory ().asLinuxFactory ()->getCairoGraphicsDeviceFactory ().removeDevice (
cairo_surface_get_device (windowSurface));
}
void onSizeChanged (const CPoint& size)
{
cairo_xcb_surface_set_size (windowSurface, size.x, size.y);
backBuffer = Cairo::SurfaceHandle (cairo_surface_create_similar (
windowSurface, CAIRO_CONTENT_COLOR_ALPHA, size.x, size.y));
backBufferSize.setSize (size);
auto cairoDevice = std::static_pointer_cast<CairoGraphicsDevice> (device);
drawContext = std::make_shared<CairoGraphicsDeviceContext> (*cairoDevice, backBuffer);
}
void draw (const CInvalidRectList& dirtyRects, IPlatformFrameCallback* frame)
{
drawContext->beginDraw ();
frame->platformDrawRects (drawContext, 1, dirtyRects.data ());
drawContext->endDraw ();
blitBackbufferToWindow (dirtyRects);
xcb_flush (RunLoop::instance ().getXcbConnection ());
}
private:
Cairo::SurfaceHandle windowSurface;
Cairo::SurfaceHandle backBuffer;
CRect backBufferSize;
std::shared_ptr<CairoGraphicsDeviceContext> drawContext;
PlatformGraphicsDevicePtr device;
void blitBackbufferToWindow (const CInvalidRectList& rects)
{
Cairo::ContextHandle windowContext (cairo_create (windowSurface));
cairo_set_source_surface (windowContext, backBuffer, 0, 0);
for (auto rect : rects)
{
cairo_rectangle (windowContext, rect.left, rect.top, rect.getWidth (),
rect.getHeight ());
cairo_clip_preserve (windowContext);
cairo_fill (windowContext);
cairo_reset_clip (windowContext);
}
cairo_surface_flush (windowSurface);
}
};
//------------------------------------------------------------------------
struct DoubleClickDetector
{
void onEvent (MouseDownUpMoveEvent& event, xcb_timestamp_t time)
{
if (event.type == EventType::MouseDown)
onMouseDown (event.mousePosition, event.buttonState, time);
if (event.type == EventType::MouseMove)
onMouseMove (event.mousePosition, event.buttonState, time);
if (event.type == EventType::MouseUp)
onMouseUp (event.mousePosition, event.buttonState, time);
if (isDoubleClick)
event.clickCount = 2;
}
private:
void onMouseDown (CPoint where, MouseEventButtonState buttonState, xcb_timestamp_t time)
{
switch (state)
{
case State::MouseDown:
case State::Uninitialized:
{
state = State::MouseDown;
firstClickState = buttonState;
firstClickTime = time;
isDoubleClick = false;
point = where;
break;
}
case State::MouseUp:
{
if (timeInside (time) && pointInside (where))
{
isDoubleClick = true;
}
state = State::Uninitialized;
break;
}
}
}
void onMouseUp (CPoint where, MouseEventButtonState buttonState, xcb_timestamp_t time)
{
if (state == State::MouseDown && pointInside (where))
state = State::MouseUp;
else
state = State::Uninitialized;
}
void onMouseMove (CPoint where, MouseEventButtonState buttonState, xcb_timestamp_t time)
{
if (!pointInside (where))
state = State::Uninitialized;
}
bool timeInside (xcb_timestamp_t time)
{
constexpr xcb_timestamp_t threshold = 250; // in milliseconds
return (time - firstClickTime) < threshold;
}
bool pointInside (CPoint p) const
{
CRect r;
r.setTopLeft (point);
r.setBottomRight (point);
r.inset (-5, -5);
return r.pointInside (p);
}
enum class State
{
Uninitialized,
MouseDown,
MouseUp,
};
State state {State::Uninitialized};
bool isDoubleClick {false};
CPoint point;
MouseEventButtonState firstClickState;
xcb_timestamp_t firstClickTime {0};
};
//------------------------------------------------------------------------
struct Frame::Impl : IFrameEventHandler
{
using RectList = CInvalidRectList;
ChildWindow window;
DrawHandler drawHandler;
DoubleClickDetector doubleClickDetector;
IPlatformFrameCallback* frame;
std::unique_ptr<GenericOptionMenuTheme> genericOptionMenuTheme;
SharedPointer<RedrawTimerHandler> redrawTimer;
RectList dirtyRects;
CCursorType currentCursor {kCursorDefault};
uint32_t pointerGrabed {0};
XdndHandler dndHandler;
//------------------------------------------------------------------------
Impl (::Window parent, CPoint size, IPlatformFrameCallback* frame)
: window (parent, size), drawHandler (window), frame (frame), dndHandler (&window, frame)
{
RunLoop::instance ().registerWindowEventHandler (window.getID (), this);
}
//------------------------------------------------------------------------
~Impl () noexcept { RunLoop::instance ().unregisterWindowEventHandler (window.getID ()); }
//------------------------------------------------------------------------
void setSize (const CRect& size)
{
window.setSize (size);
drawHandler.onSizeChanged (size.getSize ());
dirtyRects.clear ();
dirtyRects.add (size);
}
//------------------------------------------------------------------------
void setCursor (CCursorType cursor)
{
if (currentCursor == cursor)
return;
currentCursor = cursor;
setCursorInternal (cursor);
}
//------------------------------------------------------------------------
void setCursorInternal (CCursorType cursor)
{
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_params_cw_t params;
params.cursor = RunLoop::instance ().getCursorID (cursor);
xcb_aux_change_window_attributes (xcb, window.getID (), XCB_CW_CURSOR, &params);
xcb_aux_sync (xcb);
xcb_flush (xcb);
}
//------------------------------------------------------------------------
void redraw ()
{
drawHandler.draw (dirtyRects, frame);
dirtyRects.clear ();
}
//------------------------------------------------------------------------
void invalidRect (CRect r)
{
dirtyRects.add (r);
if (redrawTimer)
return;
redrawTimer = makeOwned<RedrawTimerHandler> (16, [this] () {
if (dirtyRects.data ().empty ())
return;
redraw ();
});
}
//------------------------------------------------------------------------
void grabPointer ()
{
if (++pointerGrabed > 1)
return;
auto xcb = RunLoop::instance ().getXcbConnection ();
auto cookie =
xcb_grab_pointer (xcb, false, window.getID (),
(XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_ENTER_WINDOW |
XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_POINTER_MOTION),
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE,
XCB_CURSOR_NONE, XCB_TIME_CURRENT_TIME);
if (auto reply = xcb_grab_pointer_reply (xcb, cookie, nullptr))
{
if (reply->status != XCB_GRAB_STATUS_SUCCESS)
pointerGrabed = 0;
free (reply);
}
}
//------------------------------------------------------------------------
void ungrabPointer ()
{
if (pointerGrabed == 0)
return;
if (--pointerGrabed > 0)
return;
vstgui_assert (pointerGrabed == 0);
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_ungrab_pointer (xcb, XCB_TIME_CURRENT_TIME);
}
//------------------------------------------------------------------------
void onEvent (xcb_map_notify_event_t& event) override {}
//------------------------------------------------------------------------
void onEvent (xcb_key_press_event_t& event) override
{
auto type = (event.response_type & ~0x80);
auto keyEvent = RunLoop::instance ().getCurrentKeyEvent ();
frame->platformOnEvent (keyEvent);
}
//------------------------------------------------------------------------
void onEvent (xcb_button_press_event_t& event) override
{
CPoint where (event.event_x, event.event_y);
if ((event.response_type & ~0x80) == XCB_BUTTON_PRESS) // mouse down or wheel
{
if (event.detail >= 4 && event.detail <= 7) // mouse wheel
{
MouseWheelEvent wheelEvent;
wheelEvent.mousePosition = where;
wheelEvent.modifiers = toModifiers (event.state);
switch (event.detail)
{
case 4: // up
{
wheelEvent.deltaY = 1;
break;
}
case 5: // down
{
wheelEvent.deltaY = -1;
break;
}
case 6: // left
{
wheelEvent.deltaX = -1;
break;
}
case 7: // right
{
wheelEvent.deltaX = 1;
break;
}
}
frame->platformOnEvent (wheelEvent);
}
else // mouse down
{
MouseDownEvent downEvent;
downEvent.mousePosition = where;
setupMouseEventButtons (downEvent, event.detail);
setupEventModifiers (downEvent.modifiers, event.state);
doubleClickDetector.onEvent (downEvent, event.time);
frame->platformOnEvent (downEvent);
grabPointer ();
if (downEvent.consumed)
{
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_set_input_focus (xcb, XCB_INPUT_FOCUS_PARENT, window.getID (),
XCB_CURRENT_TIME);
}
}
}
else // mouse up
{
if (event.detail >= 4 && event.detail <= 7) // mouse wheel
{
}
else
{
MouseUpEvent upEvent;
upEvent.mousePosition = where;
setupMouseEventButtons (upEvent, event.detail);
setupEventModifiers (upEvent.modifiers, event.state);
doubleClickDetector.onEvent (upEvent, event.time);
frame->platformOnEvent (upEvent);
ungrabPointer ();
}
}
}
//------------------------------------------------------------------------
void onEvent (xcb_motion_notify_event_t& event) override
{
MouseMoveEvent moveEvent;
moveEvent.mousePosition (event.event_x, event.event_y);
setupMouseEventButtons (moveEvent, event.state);
setupEventModifiers (moveEvent.modifiers, event.state);
doubleClickDetector.onEvent (moveEvent, event.time);
frame->platformOnEvent (moveEvent);
// make sure we get more motion events
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_get_motion_events (xcb, window.getID (), event.time, event.time + 10000000);
}
//------------------------------------------------------------------------
void onEvent (xcb_enter_notify_event_t& event) override
{
if ((event.response_type & ~0x80) == XCB_LEAVE_NOTIFY)
{
MouseExitEvent exitEvent;
exitEvent.mousePosition (event.event_x, event.event_y);
setupMouseEventButtons (exitEvent, event.state);
setupEventModifiers (exitEvent.modifiers, event.state);
frame->platformOnEvent (exitEvent);
setCursorInternal (kCursorDefault);
}
else
{
setCursorInternal (currentCursor);
}
}
//------------------------------------------------------------------------
void onEvent (xcb_focus_in_event_t& event) override {}
//------------------------------------------------------------------------
void onEvent (xcb_expose_event_t& event) override
{
CRect r;
r.setTopLeft (CPoint (event.x, event.y));
r.setSize (CPoint (event.width, event.height));
invalidRect (r);
}
//------------------------------------------------------------------------
void onEvent (xcb_property_notify_event_t& event) override
{
#if 1 // needed for Reaper
if (Atoms::xEmbedInfo.valid () && event.atom == Atoms::xEmbedInfo ())
{
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_map_window (xcb, window.getID ());
}
#endif
}
void onEvent (xcb_selection_notify_event_t& event) override
{
dndHandler.selectionNotify (event);
}
//------------------------------------------------------------------------
void onEvent (xcb_client_message_event_t& event, xcb_window_t proxyId = 0) override
{
if (Atoms::xEmbed.valid () && event.type == Atoms::xEmbed ())
{
switch (static_cast<XEMBED> (event.data.data32[1]))
{
case XEMBED::EMBEDDED_NOTIFY:
{
auto xcb = RunLoop::instance ().getXcbConnection ();
xcb_map_window (xcb, window.getID ());
break;
}
case XEMBED::WINDOW_ACTIVATE:
{
frame->platformOnWindowActivate (true);
break;
}
case XEMBED::WINDOW_DEACTIVATE:
{
frame->platformOnWindowActivate (false);
break;
}
case XEMBED::FOCUS_IN:
{
frame->platformOnActivate (true);
break;
}
case XEMBED::FOCUS_OUT:
{
frame->platformOnActivate (false);
break;
}
case XEMBED::FOCUS_NEXT:
{
// we could send a tab keycode here...
break;
}
case XEMBED::FOCUS_PREV:
{
// we could send a shift-tab keycode here...
break;
}
case XEMBED::MODALITY_ON:
case XEMBED::MODALITY_OFF:
case XEMBED::REGISTER_ACCELERATOR:
case XEMBED::UNREGISTER_ACCELERATOR:
case XEMBED::ACTIVATE_ACCELERATOR:
case XEMBED::REQUEST_FOCUS:
break;
}
}
else if (Atoms::xDndEnter.valid () && event.type == Atoms::xDndEnter ())
{
dndHandler.enter (event, proxyId ? proxyId : window.getID ());
}
else if (Atoms::xDndPosition.valid () && event.type == Atoms::xDndPosition ())
{
dndHandler.position (event);
}
else if (Atoms::xDndLeave.valid () && event.type == Atoms::xDndLeave ())
{
dndHandler.leave (event);
}
else if (Atoms::xDndDrop.valid () && event.type == Atoms::xDndDrop ())
{
dndHandler.drop (event);
}
}
};
//------------------------------------------------------------------------
Frame::Frame (IPlatformFrameCallback* frame, const CRect& size, uint32_t parent,
IPlatformFrameConfig* config)
: IPlatformFrame (frame)
{
auto cfg = dynamic_cast<FrameConfig*> (config);
if (cfg && cfg->runLoop)
{
RunLoop::init ();
if (auto f = getPlatformFactory ().asLinuxFactory ())
{
if (f->getRunLoop () == nullptr)
f->setRunLoop (cfg->runLoop);
}
}
impl = std::unique_ptr<Impl> (new Impl (parent, {size.getWidth (), size.getHeight ()}, frame));
frame->platformOnActivate (true);
}
//------------------------------------------------------------------------
Frame::~Frame ()
{
impl.reset ();
RunLoop::exit ();
}
//------------------------------------------------------------------------
void Frame::optionMenuPopupStarted ()
{
impl->grabPointer ();
}
//------------------------------------------------------------------------
void Frame::optionMenuPopupStopped ()
{
impl->ungrabPointer ();
}
//------------------------------------------------------------------------
bool Frame::getGlobalPosition (CPoint& pos) const
{
return false;
}
//------------------------------------------------------------------------
bool Frame::setSize (const CRect& newSize)
{
vstgui_assert (impl);
impl->setSize (newSize);
return true;
}
//------------------------------------------------------------------------
bool Frame::getSize (CRect& size) const
{
size.setSize (impl->window.getSize ());
return true;
}
//------------------------------------------------------------------------
bool Frame::getCurrentMousePosition (CPoint& mousePosition) const
{
xcb_query_pointer_cookie_t cookie =
xcb_query_pointer (RunLoop::instance ().getXcbConnection (), getX11WindowID ());
xcb_query_pointer_reply_t* reply =
xcb_query_pointer_reply (RunLoop::instance ().getXcbConnection (), cookie, nullptr);
if (!reply)
return false;
mousePosition.x = reply->win_x;
mousePosition.y = reply->win_y;
return true;
}
//------------------------------------------------------------------------
bool Frame::getCurrentMouseButtons (CButtonState& buttons) const
{
return false;
}
//------------------------------------------------------------------------
bool Frame::getCurrentModifiers (Modifiers& modifiers) const
{
return false;
}
//------------------------------------------------------------------------
bool Frame::setMouseCursor (CCursorType type)
{
impl->setCursor (type);
return true;
}
//------------------------------------------------------------------------
bool Frame::invalidRect (const CRect& rect)
{
impl->invalidRect (rect);
return true;
}
//------------------------------------------------------------------------
bool Frame::scrollRect (const CRect& src, const CPoint& distance)
{
(void)src;
(void)distance;
return false;
}
//------------------------------------------------------------------------
bool Frame::showTooltip (const CRect& rect, const char* utf8Text)
{
#warning TODO: Implementation
return false;
}
//------------------------------------------------------------------------
bool Frame::hideTooltip ()
{
#warning TODO: Implementation
return false;
}
//------------------------------------------------------------------------
void* Frame::getPlatformRepresentation () const
{
return reinterpret_cast<void*> (getX11WindowID ());
}
//------------------------------------------------------------------------
uint32_t Frame::getX11WindowID () const
{
return impl->window.getID ();
}
//------------------------------------------------------------------------
SharedPointer<IPlatformTextEdit> Frame::createPlatformTextEdit (IPlatformTextEditCallback* textEdit)
{
return makeOwned<GenericTextEdit> (textEdit);
}
//------------------------------------------------------------------------
SharedPointer<IPlatformOptionMenu> Frame::createPlatformOptionMenu ()
{
auto cFrame = dynamic_cast<CFrame*> (frame);
GenericOptionMenuTheme theme;
if (impl->genericOptionMenuTheme)
theme = *impl->genericOptionMenuTheme.get ();
auto optionMenu =
makeOwned<GenericOptionMenu> (cFrame, MouseEventButtonState (MouseButton::Left), theme);
optionMenu->setListener (this);
return optionMenu;
}
#if VSTGUI_OPENGL_SUPPORT
//------------------------------------------------------------------------
SharedPointer<IPlatformOpenGLView> Frame::createPlatformOpenGLView ()
{
#warning TODO: Implementation
return nullptr;
}
#endif
//------------------------------------------------------------------------
SharedPointer<IPlatformViewLayer> Frame::createPlatformViewLayer (
IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer)
{
// optional
return nullptr;
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
//------------------------------------------------------------------------
DragResult Frame::doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap)
{
return kDragError;
}
#endif
//------------------------------------------------------------------------
bool Frame::doDrag (const DragDescription& dragDescription,
const SharedPointer<IDragCallback>& callback)
{
return false;
}
//------------------------------------------------------------------------
PlatformType Frame::getPlatformType () const
{
return PlatformType::kX11EmbedWindowID;
}
//------------------------------------------------------------------------
Optional<UTF8String> Frame::convertCurrentKeyEventToText ()
{
return RunLoop::instance ().convertCurrentKeyEventToText ();
}
//------------------------------------------------------------------------
bool Frame::setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme)
{
if (theme)
impl->genericOptionMenuTheme =
std::unique_ptr<GenericOptionMenuTheme> (new GenericOptionMenuTheme (*theme));
else
impl->genericOptionMenuTheme = nullptr;
return true;
}
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,74 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../crect.h"
#include "../iplatformframe.h"
#include "../iplatformresourceinputstream.h"
#include "../platform_x11.h"
#include "../common/genericoptionmenu.h"
#include "irunloop.h"
#include <memory>
#include <functional>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
//------------------------------------------------------------------------
class Frame
: public IPlatformFrame
, public IX11Frame
, public IGenericOptionMenuListener
{
public:
Frame (IPlatformFrameCallback* frame, const CRect& size, uint32_t parent,
IPlatformFrameConfig* config);
~Frame ();
private:
bool getGlobalPosition (CPoint& pos) const override;
bool setSize (const CRect& newSize) override;
bool getSize (CRect& size) const override;
bool getCurrentMousePosition (CPoint& mousePosition) const override;
bool getCurrentMouseButtons (CButtonState& buttons) const override;
bool getCurrentModifiers (Modifiers& modifiers) const override;
bool setMouseCursor (CCursorType type) override;
bool invalidRect (const CRect& rect) override;
bool scrollRect (const CRect& src, const CPoint& distance) override;
bool showTooltip (const CRect& rect, const char* utf8Text) override;
bool hideTooltip () override;
void* getPlatformRepresentation () const override;
SharedPointer<IPlatformTextEdit>
createPlatformTextEdit (IPlatformTextEditCallback* textEdit) override;
SharedPointer<IPlatformOptionMenu> createPlatformOptionMenu () override;
#if VSTGUI_OPENGL_SUPPORT
SharedPointer<IPlatformOpenGLView> createPlatformOpenGLView () override;
#endif
SharedPointer<IPlatformViewLayer> createPlatformViewLayer (
IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer) override;
#if VSTGUI_ENABLE_DEPRECATED_METHODS
DragResult doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) override;
#endif
bool doDrag (const DragDescription& dragDescription,
const SharedPointer<IDragCallback>& callback) override;
PlatformType getPlatformType () const override;
void onFrameClosed () override {}
Optional<UTF8String> convertCurrentKeyEventToText () override;
bool setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme = nullptr) override;
uint32_t getX11WindowID () const override;
void optionMenuPopupStarted () override;
void optionMenuPopupStopped () override;
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,561 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "x11platform.h"
#include "linuxfactory.h"
#include "../../cfileselector.h"
#include "../../cframe.h"
#include "../../cstring.h"
#include "../../events.h"
#include "x11frame.h"
#include "x11dragging.h"
#include <cassert>
#include <chrono>
#include <array>
#include <dlfcn.h>
#include <iostream>
#include <locale>
#include <link.h>
#include <unordered_map>
#include <codecvt>
#include <xcb/xcb.h>
#include <xcb/xcb_cursor.h>
#include <xcb/xcb_util.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xcb_aux.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <X11/Xlib.h>
// c++11 compile error workaround
#define explicit _explicit
#include <xcb/xkb.h>
#undef explicit
#undef None
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
namespace X11 {
//------------------------------------------------------------------------
namespace {
using VirtMap = std::unordered_map<xkb_keysym_t, VirtualKey>;
const VirtMap keyMap = {{XKB_KEY_BackSpace, VirtualKey::Back},
{XKB_KEY_Tab, VirtualKey::Tab},
{XKB_KEY_Clear, VirtualKey::Clear},
{XKB_KEY_Return, VirtualKey::Return},
{XKB_KEY_Pause, VirtualKey::Pause},
{XKB_KEY_Escape, VirtualKey::Escape},
{XKB_KEY_space, VirtualKey::Space},
{XKB_KEY_End, VirtualKey::End},
{XKB_KEY_Home, VirtualKey::Home},
{XKB_KEY_Left, VirtualKey::Left},
{XKB_KEY_Up, VirtualKey::Up},
{XKB_KEY_Right, VirtualKey::Right},
{XKB_KEY_Down, VirtualKey::Down},
{XKB_KEY_Page_Up, VirtualKey::PageUp},
{XKB_KEY_Page_Down, VirtualKey::PageDown},
{XKB_KEY_Select, VirtualKey::Select},
{XKB_KEY_Print, VirtualKey::Print},
{XKB_KEY_KP_Enter, VirtualKey::Enter},
{XKB_KEY_Insert, VirtualKey::Insert},
{XKB_KEY_Delete, VirtualKey::Delete},
{XKB_KEY_Help, VirtualKey::Help},
// Numpads ???
{XKB_KEY_KP_Multiply, VirtualKey::Multiply},
{XKB_KEY_KP_Add, VirtualKey::Add},
{XKB_KEY_KP_Separator, VirtualKey::Separator},
{XKB_KEY_KP_Subtract, VirtualKey::Subtract},
{XKB_KEY_KP_Decimal, VirtualKey::Decimal},
{XKB_KEY_KP_Divide, VirtualKey::Divide},
{XKB_KEY_F1, VirtualKey::F1},
{XKB_KEY_F2, VirtualKey::F2},
{XKB_KEY_F3, VirtualKey::F3},
{XKB_KEY_F4, VirtualKey::F4},
{XKB_KEY_F5, VirtualKey::F5},
{XKB_KEY_F6, VirtualKey::F6},
{XKB_KEY_F7, VirtualKey::F7},
{XKB_KEY_F8, VirtualKey::F8},
{XKB_KEY_F9, VirtualKey::F9},
{XKB_KEY_F10, VirtualKey::F10},
{XKB_KEY_F11, VirtualKey::F11},
{XKB_KEY_F12, VirtualKey::F12},
{XKB_KEY_Num_Lock, VirtualKey::NumLock},
{XKB_KEY_Scroll_Lock, VirtualKey::Scroll}, // correct ?
#if 0
{XKB_KEY_Shift_L, VirtualKey::SHIFT},
{XKB_KEY_Shift_R, VirtualKey::SHIFT},
{XKB_KEY_Control_L, VirtualKey::CONTROL},
{XKB_KEY_Control_R, VirtualKey::CONTROL},
{XKB_KEY_Alt_L, VirtualKey::ALT},
{XKB_KEY_Alt_R, VirtualKey::ALT},
#endif
{XKB_KEY_VoidSymbol, VirtualKey::None}};
const VirtMap shiftKeyMap = {{XKB_KEY_KP_Page_Up, VirtualKey::PageUp},
{XKB_KEY_KP_Page_Down, VirtualKey::PageDown},
{XKB_KEY_KP_Home, VirtualKey::Home},
{XKB_KEY_KP_End, VirtualKey::End}};
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
struct RunLoop::Impl : IEventHandler
{
using WindowEventHandlerMap = std::unordered_map<uint32_t, IFrameEventHandler*>;
std::atomic<uint32_t> useCount {0};
xcb_connection_t* xcbConnection {nullptr};
xcb_cursor_context_t* cursorContext {nullptr};
xkb_context* xkbContext {nullptr};
xkb_state* xkbState {nullptr};
xkb_state* xkbUnprocessedState {nullptr};
xkb_keymap* xkbKeymap {nullptr};
WindowEventHandlerMap windowEventHandlerMap;
std::array<xcb_cursor_t, CCursorType::kCursorIBeam + 1> cursors {{XCB_CURSOR_NONE}};
KeyboardEvent lastUnprocessedKeyEvent;
uint32_t lastUtf32KeyEventChar {0};
void init ()
{
if (++useCount != 1)
return;
int screenNo;
xcbConnection = xcb_connect (nullptr, &screenNo);
RunLoop::get ()->registerEventHandler (xcb_get_file_descriptor (xcbConnection), this);
auto screen = xcb_aux_get_screen (xcbConnection, screenNo);
xcb_cursor_context_new (xcbConnection, screen, &cursorContext);
xcb_xkb_use_extension (xcbConnection, XKB_X11_MIN_MAJOR_XKB_VERSION,
XKB_X11_MIN_MINOR_XKB_VERSION);
xkbContext = xkb_context_new (XKB_CONTEXT_NO_FLAGS);
int32_t deviceId = xkb_x11_get_core_keyboard_device_id (xcbConnection);
if (deviceId > -1)
{
xkbKeymap = xkb_x11_keymap_new_from_device (xkbContext, xcbConnection, deviceId,
XKB_KEYMAP_COMPILE_NO_FLAGS);
xkbState = xkb_state_new (xkbKeymap);
xkbUnprocessedState = xkb_state_new (xkbKeymap);
auto xkbStateCookie = xcb_xkb_get_state (xcbConnection, deviceId);
auto* xkbStateReply = xcb_xkb_get_state_reply (xcbConnection, xkbStateCookie, nullptr);
if (xkbStateReply)
{
xkb_state_update_mask (xkbState,
xkbStateReply->baseMods,
xkbStateReply->latchedMods,
xkbStateReply->lockedMods,
xkbStateReply->baseGroup,
xkbStateReply->latchedGroup,
xkbStateReply->lockedGroup);
free (xkbStateReply);
}
}
}
void exit ()
{
if (--useCount != 0)
return;
if (xcbConnection)
{
if (xkbUnprocessedState)
xkb_state_unref (xkbUnprocessedState);
if (xkbState)
xkb_state_unref (xkbState);
if (xkbKeymap)
xkb_keymap_unref (xkbKeymap);
if (xkbContext)
xkb_context_unref (xkbContext);
if (cursorContext)
{
for (auto c : cursors)
{
if (c != XCB_CURSOR_NONE)
xcb_free_cursor (xcbConnection, c);
}
xcb_cursor_context_free (cursorContext);
}
xcb_disconnect (xcbConnection);
}
RunLoop::get ()->unregisterEventHandler (this);
}
template<typename T>
void dispatchEvent (T& event, xcb_window_t windowId)
{
auto it = windowEventHandlerMap.find (windowId);
if (it != windowEventHandlerMap.end ())
{
it->second->onEvent (event);
return;
}
// we may receive a proxied drag-and-drop event; if that is the case,
// the window has the attribute XdndProxy, and acts as a proxy for the
// other window designated by the value of this attribute.
if (std::is_same<T, xcb_client_message_event_t>::value)
{
xcb_client_message_event_t& cmsg =
reinterpret_cast<xcb_client_message_event_t&> (event);
if (isXdndClientMessage (cmsg))
{
xcb_window_t targetId = getXdndProxy (windowId);
if (targetId != 0)
it = windowEventHandlerMap.find (targetId);
if (it != windowEventHandlerMap.end ())
{
it->second->onEvent (cmsg, windowId);
return;
}
}
}
}
//------------------------------------------------------------------------
void onKeyEvent (const xcb_key_press_event_t& event, bool isKeyDown)
{
if (!xkbUnprocessedState)
return;
KeyboardEvent keyEvent;
keyEvent.type = isKeyDown ? EventType::KeyDown : EventType::KeyUp;
if (event.state & XCB_MOD_MASK_SHIFT)
keyEvent.modifiers.add (ModifierKey::Shift);
if (event.state & XCB_MOD_MASK_CONTROL)
keyEvent.modifiers.add (ModifierKey::Control);
if (event.state & (XCB_MOD_MASK_1 | XCB_MOD_MASK_5))
keyEvent.modifiers.add (ModifierKey::Alt);
auto ksym = xkb_state_key_get_one_sym (xkbUnprocessedState, event.detail);
xkb_state_update_key (xkbState, event.detail, isKeyDown ? XKB_KEY_DOWN : XKB_KEY_UP);
VirtMap::const_iterator it;
bool ksymMapped = false;
if (!ksymMapped && keyEvent.modifiers.has(ModifierKey::Shift))
{
it = shiftKeyMap.find (ksym);
ksymMapped = it != shiftKeyMap.end ();
}
if (!ksymMapped)
{
it = keyMap.find (ksym);
ksymMapped = it != keyMap.end ();
}
if (ksymMapped)
{
keyEvent.virt = it->second;
lastUtf32KeyEventChar = 0;
}
else
{
keyEvent.character = xkb_state_key_get_utf32 (xkbState, event.detail);
lastUtf32KeyEventChar = keyEvent.character;
}
lastUnprocessedKeyEvent = std::move (keyEvent);
}
void onEvent () override
{
while (auto event = xcb_poll_for_event (xcbConnection))
{
auto type = event->response_type & ~0x80;
switch (type)
{
case XCB_KEY_PRESS:
{
auto ev = reinterpret_cast<xcb_key_press_event_t*> (event);
onKeyEvent (*ev, true);
dispatchEvent (*ev, ev->event);
break;
}
case XCB_KEY_RELEASE:
{
auto ev = reinterpret_cast<xcb_key_release_event_t*> (event);
onKeyEvent (*ev, false);
dispatchEvent (*ev, ev->event);
break;
}
case XCB_BUTTON_PRESS:
{
auto ev = reinterpret_cast<xcb_button_press_event_t*> (event);
dispatchEvent (*ev, ev->event);
break;
}
case XCB_BUTTON_RELEASE:
{
auto ev = reinterpret_cast<xcb_button_release_event_t*> (event);
dispatchEvent (*ev, ev->event);
break;
}
case XCB_MOTION_NOTIFY:
{
auto ev = reinterpret_cast<xcb_motion_notify_event_t*> (event);
dispatchEvent (*ev, ev->event);
break;
}
case XCB_ENTER_NOTIFY:
{
auto ev = reinterpret_cast<xcb_enter_notify_event_t*> (event);
dispatchEvent (*ev, ev->event);
break;
}
case XCB_LEAVE_NOTIFY:
{
auto ev = reinterpret_cast<xcb_leave_notify_event_t*> (event);
dispatchEvent (*ev, ev->event);
break;
}
case XCB_EXPOSE:
{
auto ev = reinterpret_cast<xcb_expose_event_t*> (event);
dispatchEvent (*ev, ev->window);
break;
}
case XCB_UNMAP_NOTIFY:
{
auto ev = reinterpret_cast<xcb_unmap_notify_event_t*> (event);
break;
}
case XCB_MAP_NOTIFY:
{
auto ev = reinterpret_cast<xcb_map_notify_event_t*> (event);
dispatchEvent (*ev, ev->window);
break;
}
case XCB_CONFIGURE_NOTIFY:
{
auto ev = reinterpret_cast<xcb_configure_notify_event_t*> (event);
break;
}
case XCB_PROPERTY_NOTIFY:
{
auto ev = reinterpret_cast<xcb_property_notify_event_t*> (event);
dispatchEvent (*ev, ev->window);
break;
}
case XCB_SELECTION_NOTIFY:
{
auto ev = reinterpret_cast<xcb_selection_notify_event_t*> (event);
dispatchEvent (*ev, ev->requestor);
break;
}
case XCB_CLIENT_MESSAGE:
{
auto ev = reinterpret_cast<xcb_client_message_event_t*> (event);
dispatchEvent (*ev, ev->window);
break;
}
case XCB_FOCUS_IN:
case XCB_FOCUS_OUT:
{
auto ev = reinterpret_cast<xcb_focus_in_event_t*> (event);
dispatchEvent (*ev, ev->event);
break;
}
}
std::free (event);
}
xcb_aux_sync (xcbConnection);
xcb_flush (xcbConnection);
}
};
//------------------------------------------------------------------------
RunLoop& RunLoop::instance ()
{
static RunLoop gInstance;
return gInstance;
}
//------------------------------------------------------------------------
void RunLoop::init () { instance ().impl->init (); }
//------------------------------------------------------------------------
void RunLoop::exit ()
{
instance ().impl->exit ();
}
//------------------------------------------------------------------------
const SharedPointer<IRunLoop> RunLoop::get ()
{
return getPlatformFactory ().asLinuxFactory ()->getRunLoop ();
}
//------------------------------------------------------------------------
RunLoop::RunLoop ()
{
impl = std::unique_ptr<Impl> (new Impl);
}
//------------------------------------------------------------------------
RunLoop::~RunLoop () noexcept = default;
//------------------------------------------------------------------------
void RunLoop::registerWindowEventHandler (uint32_t windowId, IFrameEventHandler* handler)
{
impl->windowEventHandlerMap.emplace (windowId, handler);
}
//------------------------------------------------------------------------
void RunLoop::unregisterWindowEventHandler (uint32_t windowId)
{
auto it = impl->windowEventHandlerMap.find (windowId);
if (it == impl->windowEventHandlerMap.end ())
return;
impl->windowEventHandlerMap.erase (it);
}
//------------------------------------------------------------------------
xcb_connection_t* RunLoop::getXcbConnection () const
{
return impl->xcbConnection;
}
//------------------------------------------------------------------------
namespace {
template<typename T>
uint32_t makeCursor (xcb_cursor_context_t* context, const T& names)
{
for (auto& name : names)
{
auto result = xcb_cursor_load_cursor (context, name);
if (result != XCB_CURSOR_NONE)
return result;
}
return XCB_CURSOR_NONE;
}
template<size_t count>
using CharPtrArray = std::array<const char*, count>;
constexpr auto CursorDefaultNames = //
CharPtrArray<4> {"left_ptr", "arrow", "dnd-none", "op_left_arrow"};
constexpr auto CursorWaitNames = //
CharPtrArray<3> {"wait", "watch", "progress"};
constexpr auto CursorHSizeNames = //
CharPtrArray<8> {"size_hor", "sb_h_double_arrow", "h_double_arrow", "e-resize",
"w-resize", "row-resize", "right_side", "left_side"};
constexpr auto CursorVSizeNames = //
CharPtrArray<12> {"size_ver", "sb_v_double_arrow", "v_double_arrow", "n-resize",
"s-resize", "col-resize", "top_side", "bottom_side",
"base_arrow_up", "base_arrow_down", "based_arrow_down", "based_arrow_up"};
constexpr auto CursorNESWSizeNames = //
CharPtrArray<5> {"size_bdiag", "fd_double_arrow", "bottom_left_corner", "top_right_corner"};
constexpr auto CursorNWSESizeNames = //
CharPtrArray<5> {"size_fdiag", "bd_double_arrow", "bottom_right_corner", "top_left_corner"};
constexpr auto CursorSizeAllNames = //
CharPtrArray<4> {"cross", "diamond-cross", "cross-reverse", "crosshair"};
constexpr auto CursorCopyNames = //
CharPtrArray<2> {"dnd-copy", "copy"};
constexpr auto CursorNotAllowedNames = //
CharPtrArray<4> {"forbidden", "circle", "dnd-no-drop", "not-allowed"};
constexpr auto CursorHandNames = //
CharPtrArray<4> {"openhand", "hand1", "all_scroll", "all-scroll"};
constexpr auto CursorIBeamNames = //
CharPtrArray<3> {"ibeam", "xterm", "text"};
constexpr auto CursorMovableObjectNames = //
CharPtrArray<2> {"hand1", "fleur"};
constexpr auto CursorMoveObjectNames = //
CharPtrArray<2> {"fleur", "hand1"};
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
uint32_t RunLoop::getCursorID (CCursorType cursor)
{
if (impl->cursors[cursor] == XCB_CURSOR_NONE && impl->cursorContext)
{
uint32_t cursorID = XCB_CURSOR_NONE;
switch (cursor)
{
case kCursorDefault:
cursorID = makeCursor (impl->cursorContext, CursorDefaultNames);
break;
case kCursorWait:
cursorID = makeCursor (impl->cursorContext, CursorWaitNames);
break;
case kCursorHSize:
cursorID = makeCursor (impl->cursorContext, CursorHSizeNames);
break;
case kCursorVSize:
cursorID = makeCursor (impl->cursorContext, CursorVSizeNames);
break;
case kCursorNESWSize:
cursorID = makeCursor (impl->cursorContext, CursorNESWSizeNames);
break;
case kCursorNWSESize:
cursorID = makeCursor (impl->cursorContext, CursorNWSESizeNames);
break;
case kCursorCrosshair:
case kCursorSizeAll:
cursorID = makeCursor (impl->cursorContext, CursorSizeAllNames);
break;
case kCursorCopy:
cursorID = makeCursor (impl->cursorContext, CursorCopyNames);
break;
case kCursorNotAllowed:
cursorID = makeCursor (impl->cursorContext, CursorNotAllowedNames);
break;
case kCursorPointingHand:
cursorID = makeCursor (impl->cursorContext, CursorHandNames);
break;
case kCursorIBeam:
cursorID = makeCursor (impl->cursorContext, CursorIBeamNames);
break;
case kCursorMovableObject:
cursorID = makeCursor (impl->cursorContext, CursorMovableObjectNames);
break;
case kCursorMoveObject:
cursorID = makeCursor (impl->cursorContext, CursorMoveObjectNames);
break;
}
impl->cursors[cursor] = cursorID;
}
return impl->cursors[cursor];
}
//------------------------------------------------------------------------
KeyboardEvent&& RunLoop::getCurrentKeyEvent () const
{
return std::move (impl->lastUnprocessedKeyEvent);
}
//------------------------------------------------------------------------
Optional<UTF8String> RunLoop::convertCurrentKeyEventToText () const
{
if (impl->lastUtf32KeyEventChar == 0)
return {};
try
{
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> conv;
return Optional<UTF8String> (UTF8String (conv.to_bytes (impl->lastUtf32KeyEventChar)));
}
catch (...)
{
}
return {};
}
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,79 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../vstguifwd.h"
#include "x11frame.h"
#include <atomic>
#include <memory>
#include <cairo/cairo.h>
struct xcb_connection_t; // forward declaration
struct xcb_key_press_event_t; // forward declaration
struct xcb_button_press_event_t;
struct xcb_motion_notify_event_t;
struct xcb_enter_notify_event_t;
struct xcb_focus_in_event_t;
struct xcb_expose_event_t;
struct xcb_map_notify_event_t;
struct xcb_property_notify_event_t;
struct xcb_selection_notify_event_t;
struct xcb_client_message_event_t;
using xcb_window_t = uint32_t;
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
namespace X11 {
class Frame;
class Timer;
//------------------------------------------------------------------------
struct IFrameEventHandler
{
virtual void onEvent (xcb_map_notify_event_t& event) = 0;
virtual void onEvent (xcb_key_press_event_t& event) = 0;
virtual void onEvent (xcb_button_press_event_t& event) = 0;
virtual void onEvent (xcb_motion_notify_event_t& event) = 0;
virtual void onEvent (xcb_enter_notify_event_t& event) = 0;
virtual void onEvent (xcb_focus_in_event_t& event) = 0;
virtual void onEvent (xcb_expose_event_t& event) = 0;
virtual void onEvent (xcb_property_notify_event_t& event) = 0;
virtual void onEvent (xcb_selection_notify_event_t& event) = 0;
virtual void onEvent (xcb_client_message_event_t& event, xcb_window_t proxyId = 0) = 0;
};
//------------------------------------------------------------------------
struct RunLoop
{
static void init ();
static void exit ();
static const SharedPointer<IRunLoop> get ();
xcb_connection_t* getXcbConnection () const;
void registerWindowEventHandler (uint32_t windowId, IFrameEventHandler* handler);
void unregisterWindowEventHandler (uint32_t windowId);
uint32_t getCursorID (CCursorType cursor);
KeyboardEvent&& getCurrentKeyEvent () const;
Optional<UTF8String> convertCurrentKeyEventToText () const;
void setDevice (cairo_device_t* device);
static RunLoop& instance ();
private:
RunLoop ();
~RunLoop () noexcept;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,53 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "x11timer.h"
#include "x11platform.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
//------------------------------------------------------------------------
Timer::Timer (IPlatformTimerCallback* _callback)
{
callback = _callback;
}
//------------------------------------------------------------------------
Timer::~Timer () noexcept
{
stop ();
}
//------------------------------------------------------------------------
bool Timer::start (uint32_t periodMs)
{
auto runLoop = RunLoop::get ();
vstgui_assert (runLoop, "Timer only works of run loop was set");
if (!runLoop)
return false;
return runLoop->registerTimer (periodMs, this);
}
//------------------------------------------------------------------------
bool Timer::stop ()
{
auto runLoop = RunLoop::get ();
vstgui_assert (runLoop, "Timer only works of run loop was set");
if (!runLoop)
return false;
return runLoop->unregisterTimer (this);
}
//------------------------------------------------------------------------
void Timer::onTimer ()
{
if (callback)
callback->fire ();
}
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,32 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformtimer.h"
#include "x11frame.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
//------------------------------------------------------------------------
class Timer : public IPlatformTimer, public ITimerHandler
{
public:
Timer (IPlatformTimerCallback* callback);
~Timer () noexcept;
bool start (uint32_t periodMs) override;
bool stop () override;
void onTimer () override;
private:
IPlatformTimerCallback* callback = nullptr;
};
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,197 @@
#include "x11utils.h"
#include <xcb/xcb.h>
#include <xcb/xcb_util.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
namespace {
//------------------------------------------------------------------------
static xcb_visualtype_t* getVisualType (const xcb_screen_t* screen)
{
auto depth_iter = xcb_screen_allowed_depths_iterator (screen);
for (; depth_iter.rem; xcb_depth_next (&depth_iter))
{
xcb_visualtype_iterator_t visual_iter;
visual_iter = xcb_depth_visuals_iterator (depth_iter.data);
for (; visual_iter.rem; xcb_visualtype_next (&visual_iter))
{
if (screen->root_visual == visual_iter.data->visual_id)
{
return visual_iter.data;
}
}
}
return nullptr;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
ChildWindow::ChildWindow (::Window parentId, CPoint size)
: size (size), id (xcb_generate_id (RunLoop::instance ().getXcbConnection ()))
{
auto connection = RunLoop::instance ().getXcbConnection ();
auto setup = xcb_get_setup (connection);
auto iter = xcb_setup_roots_iterator (setup);
auto screen = iter.data;
visual = getVisualType (screen);
#if 0
parentId = screen->root;
#endif
uint32_t paramMask = XCB_CW_BACK_PIXMAP | XCB_CW_BACKING_STORE | XCB_CW_EVENT_MASK;
xcb_params_cw_t params{};
params.back_pixel = XCB_BACK_PIXMAP_NONE;
params.backing_store = XCB_BACKING_STORE_WHEN_MAPPED;
params.event_mask =
XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT |
XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_FOCUS_CHANGE;
xcb_aux_create_window (connection, XCB_COPY_FROM_PARENT, getID (), parentId, 0, 0, size.x,
size.y, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
paramMask, &params);
// setup XEMBED
if (Atoms::xEmbedInfo.valid ())
{
XEmbedInfo info;
xcb_change_property (connection, XCB_PROP_MODE_REPLACE, getID (), Atoms::xEmbedInfo (),
Atoms::xEmbedInfo (), 32, 2, &info);
}
// setup Xdnd
if (Atoms::xDndAware.valid ())
{
uint32_t version = 5;
xcb_change_property (connection, XCB_PROP_MODE_REPLACE, getID (), Atoms::xDndAware (),
XCB_ATOM_ATOM, 32, 1, &version);
}
if (Atoms::xDndProxy.valid ())
{
uint32_t proxy = getID ();
xcb_change_property (connection, XCB_PROP_MODE_REPLACE, getID (), Atoms::xDndProxy (),
XCB_ATOM_WINDOW, 32, 1, &proxy);
}
xcb_flush (connection);
}
//------------------------------------------------------------------------
ChildWindow::~ChildWindow () noexcept {}
//------------------------------------------------------------------------
xcb_window_t ChildWindow::getID () const
{
return id;
}
//------------------------------------------------------------------------
xcb_visualtype_t* ChildWindow::getVisual () const
{
return visual;
}
//------------------------------------------------------------------------
void ChildWindow::setSize (const CRect& rect)
{
size = rect.getSize ();
auto connection = RunLoop::instance ().getXcbConnection ();
uint16_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
uint32_t values[] = {static_cast<uint32_t> (rect.left), static_cast<uint32_t> (rect.top),
static_cast<uint32_t> (rect.getWidth ()),
static_cast<uint32_t> (rect.getHeight ())};
xcb_configure_window (connection, getID (), mask, values);
xcb_flush (connection);
}
//------------------------------------------------------------------------
const CPoint& ChildWindow::getSize () const
{
return size;
}
//------------------------------------------------------------------------
Atom::Atom (const char* name) : name (name) {}
//------------------------------------------------------------------------
bool Atom::valid () const
{
create ();
return value ? true : false;
}
//------------------------------------------------------------------------
auto Atom::operator() () const -> xcb_atom_t
{
create ();
return *value;
}
//------------------------------------------------------------------------
void Atom::create () const
{
if (value)
return;
auto connection = RunLoop::instance ().getXcbConnection ();
auto cookie = xcb_intern_atom (connection, 0, name.size (), name.data ());
if (auto reply = xcb_intern_atom_reply (connection, cookie, nullptr))
{
value = Optional<xcb_atom_t> (reply->atom);
free (reply);
}
}
//------------------------------------------------------------------------
namespace Atoms {
Atom xEmbedInfo ("_XEMBED_INFO");
Atom xEmbed ("_XEMBED");
Atom xDndAware ("XdndAware");
Atom xDndProxy ("XdndProxy");
Atom xDndEnter ("XdndEnter");
Atom xDndPosition ("XdndPosition");
Atom xDndLeave ("XdndLeave");
Atom xDndStatus ("XdndStatus");
Atom xDndDrop ("XdndDrop");
Atom xDndTypeList ("XdndTypeList");
Atom xDndSelection ("XdndSelection");
Atom xDndFinished ("XdndFinished");
Atom xDndActionCopy ("XdndActionCopy");
Atom xDndActionMove ("XdndActionMove");
Atom xMimeTypeTextPlain ("text/plain");
Atom xMimeTypeTextPlainUtf8 ("text/plain;charset=utf-8");
Atom xMimeTypeUriList ("text/uri-list");
Atom xMimeTypeApplicationOctetStream ("application/octet-stream");
Atom xVstguiSelection ("XVSTGUISelection");
}
//------------------------------------------------------------------------
std::string getAtomName (xcb_atom_t atom)
{
std::string name;
auto xcb = RunLoop::instance ().getXcbConnection ();
auto cookie = xcb_get_atom_name (xcb, atom);
if (auto reply = xcb_get_atom_name_reply (xcb, cookie, nullptr))
{
auto length = xcb_get_atom_name_name_length (reply);
name.assign (
xcb_get_atom_name_name (reply),
xcb_get_atom_name_name_length (reply));
free (reply);
}
return name;
}
//------------------------------------------------------------------------
} // X11
} // VSTGUI
@@ -0,0 +1,114 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../optional.h"
#include "x11platform.h"
#include <X11/Xlib.h>
#include <string>
struct xcb_visualtype_t;
//------------------------------------------------------------------------
namespace VSTGUI {
namespace X11 {
//------------------------------------------------------------------------
struct ChildWindow
{
using xcb_window_t = uint32_t;
ChildWindow (::Window parentId, CPoint size);
~ChildWindow () noexcept;
xcb_window_t getID () const;
xcb_visualtype_t* getVisual () const;
void setSize (const CRect& rect);
const CPoint& getSize () const;
private:
xcb_window_t id;
CPoint size;
xcb_visualtype_t* visual{nullptr};
};
//------------------------------------------------------------------------
struct Atom
{
using xcb_atom_t = uint32_t;
Atom (const char* name);
bool valid () const;
xcb_atom_t operator() () const;
private:
void create () const;
std::string name;
mutable Optional<xcb_atom_t> value;
};
//------------------------------------------------------------------------
struct XEmbedInfo
{
uint32_t version{1};
uint32_t flags{0};
};
//------------------------------------------------------------------------
/* XEMBED messages */
enum class XEMBED
{
EMBEDDED_NOTIFY = 0,
WINDOW_ACTIVATE = 1,
WINDOW_DEACTIVATE = 2,
REQUEST_FOCUS = 3,
FOCUS_IN = 4,
FOCUS_OUT = 5,
FOCUS_NEXT = 6,
FOCUS_PREV = 7,
/* 8-9 were used for GRAB_KEY/UNGRAB_KEY */
MODALITY_ON = 10,
MODALITY_OFF = 11,
REGISTER_ACCELERATOR = 12,
UNREGISTER_ACCELERATOR = 13,
ACTIVATE_ACCELERATOR = 14,
};
//------------------------------------------------------------------------
namespace Atoms {
extern Atom xEmbedInfo;
extern Atom xEmbed;
extern Atom xDndAware;
extern Atom xDndProxy;
extern Atom xDndEnter;
extern Atom xDndPosition;
extern Atom xDndLeave;
extern Atom xDndStatus;
extern Atom xDndDrop;
extern Atom xDndTypeList;
extern Atom xDndSelection;
extern Atom xDndFinished;
extern Atom xDndActionCopy;
extern Atom xDndActionMove;
extern Atom xMimeTypeTextPlain;
extern Atom xMimeTypeTextPlainUtf8;
extern Atom xMimeTypeUriList;
extern Atom xMimeTypeApplicationOctetStream;
extern Atom xVstguiSelection;
//------------------------------------------------------------------------
}
using xcb_atom_t = uint32_t;
std::string getAtomName (xcb_atom_t atom);
} // X11
} // VSTGUI
@@ -0,0 +1,53 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformviewlayer.h"
#if MAC_COCOA
#include "../platform_macos.h"
#include <functional>
namespace VSTGUI {
//------------------------------------------------------------------------
struct ICAViewLayerPrivate
{
virtual ~ICAViewLayerPrivate () = default;
virtual void drawLayer (void* cgContext) = 0;
};
//-----------------------------------------------------------------------------
class CAViewLayer : public IPlatformViewLayer,
public ICocoaViewLayer,
private ICAViewLayerPrivate
//-----------------------------------------------------------------------------
{
public:
CAViewLayer (CALayer* parent);
~CAViewLayer () noexcept override;
bool init (IPlatformViewLayerDelegate* drawDelegate);
void invalidRect (const CRect& size) override;
void setSize (const CRect& size) override;
void setZIndex (uint32_t zIndex) override;
void setAlpha (float alpha) override;
void onScaleFactorChanged (double newScaleFactor) override;
CALayer* getCALayer () const override { return layer; }
//-----------------------------------------------------------------------------
private:
void drawLayer (void* cgContext) final;
CALayer* layer {nullptr};
IPlatformViewLayerDelegate* drawDelegate {nullptr};
};
} // VSTGUI
#endif // MAC_COCOA
@@ -0,0 +1,264 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "caviewlayer.h"
#if MAC_COCOA
#import "coregraphicsdevicecontext.h"
#import "macglobals.h"
#import <QuartzCore/QuartzCore.h>
#if __clang__
#if __clang_major__ >= 3 && __has_feature(objc_arc)
#define ARC_ENABLED 1
#endif // __has_feature(objc_arc)
#endif // __clang__
#if TARGET_OS_IPHONE
//-----------------------------------------------------------------------------
@interface VSTGUI_CALayer : CALayer
//-----------------------------------------------------------------------------
{
VSTGUI::ICAViewLayerPrivate* _viewLayer;
}
@end
//-----------------------------------------------------------------------------
@implementation VSTGUI_CALayer
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
- (id)init
{
self = [super init];
if (self)
{
self.needsDisplayOnBoundsChange = YES;
}
return self;
}
//-----------------------------------------------------------------------------
- (id<CAAction>)actionForKey:(NSString *)event
{
// no implicit animations
return nil;
}
//-----------------------------------------------------------------------------
- (void)setCAViewLayer:(VSTGUI::ICAViewLayerPrivate*)viewLayer
{
_viewLayer = viewLayer;
}
//-----------------------------------------------------------------------------
- (void)drawInContext:(CGContextRef)ctx
{
if (_viewLayer)
_viewLayer->drawLayer (ctx);
}
@end
#else
#import "cocoa/cocoahelpers.h"
#import "cocoa/autoreleasepool.h"
#import "cocoa/objcclassbuilder.h"
//-----------------------------------------------------------------------------
@interface VSTGUI_CALayer : CALayer
//-----------------------------------------------------------------------------
- (void)setCAViewLayer:(VSTGUI::ICAViewLayerPrivate*)viewLayer;
@end
//-----------------------------------------------------------------------------
struct VSTGUI_macOS_CALayer : VSTGUI::RuntimeObjCClass<VSTGUI_macOS_CALayer>
{
static constexpr const auto viewLayerVarName = "_viewLayer";
//-----------------------------------------------------------------------------
static Class CreateClass ()
{
return VSTGUI::ObjCClassBuilder ()
.init ("VSTGUI_CALayer", [CALayer class])
.addMethod (@selector (init), Init)
.addMethod (@selector (actionForKey:), ActionForKey)
.addMethod (@selector (setCAViewLayer:), SetCAViewLayer)
.addMethod (@selector (drawInContext:), DrawInContext)
.addIvar<VSTGUI::ICAViewLayerPrivate*> (viewLayerVarName)
.finalize ();
}
//-----------------------------------------------------------------------------
static id Init (id self, SEL _cmd)
{
self = makeInstance (self).callSuper<id (id, SEL), id> (_cmd);
if (self)
{
[self setNeedsDisplayOnBoundsChange:YES];
}
return self;
}
//-----------------------------------------------------------------------------
static id<CAAction> ActionForKey (id self, SEL _cmd, NSString* event) { return nil; }
//-----------------------------------------------------------------------------
static void SetCAViewLayer (id self, SEL _cmd, VSTGUI::CAViewLayer* viewLayer)
{
using namespace VSTGUI;
if (auto var = makeInstance (self).getVariable<VSTGUI::CAViewLayer*> (viewLayerVarName))
var->set (viewLayer);
}
//-----------------------------------------------------------------------------
static void DrawInContext (id self, SEL _cmd, CGContextRef ctx)
{
using namespace VSTGUI;
if (auto var =
makeInstance (self).getVariable<VSTGUI::ICAViewLayerPrivate*> (viewLayerVarName);
var.has_value ())
{
var->get ()->drawLayer (ctx);
}
}
};
#endif
namespace VSTGUI {
//-----------------------------------------------------------------------------
CAViewLayer::CAViewLayer (CALayer* parent)
{
#if !TARGET_OS_IPHONE
layer = [VSTGUI_macOS_CALayer::alloc () init];
#else
layer = [VSTGUI_CALayer new];
#endif
[layer setContentsScale:parent.contentsScale];
[parent addSublayer:layer];
[(id)layer setCAViewLayer:this];
}
//-----------------------------------------------------------------------------
CAViewLayer::~CAViewLayer () noexcept
{
if (layer)
{
[layer removeFromSuperlayer];
#if !ARC_ENABLED
[layer release];
#endif
}
}
//-----------------------------------------------------------------------------
bool CAViewLayer::init (IPlatformViewLayerDelegate* delegate)
{
drawDelegate = delegate;
return true;
}
//-----------------------------------------------------------------------------
void CAViewLayer::invalidRect (const CRect& size)
{
if (layer)
{
CGRect r = CGRectFromCRect (size);
if (layer.contentsAreFlipped == layer.isGeometryFlipped)
{
r.origin.y = (-r.origin.y - r.size.height) + layer.frame.size.height;
}
[layer setNeedsDisplayInRect:r];
}
}
//-----------------------------------------------------------------------------
void CAViewLayer::setSize (const CRect& size)
{
CRect r (size);
r.makeIntegral ();
CGRect cgRect = CGRectFromCRect (r);
if (layer.contentsAreFlipped == layer.isGeometryFlipped)
{
CGRect parentSize = layer.superlayer.frame;
cgRect.origin.y = (-cgRect.origin.y - cgRect.size.height) + parentSize.size.height;
}
if (CGRectEqualToRect (layer.frame, cgRect) == false)
layer.frame = cgRect;
}
//-----------------------------------------------------------------------------
void CAViewLayer::setZIndex (uint32_t zIndex)
{
if (layer)
layer.zPosition = static_cast<CGFloat>(zIndex);
}
//-----------------------------------------------------------------------------
void CAViewLayer::setAlpha (float alpha)
{
if (layer)
layer.opacity = alpha;
}
//-----------------------------------------------------------------------------
void CAViewLayer::onScaleFactorChanged (double newScaleFactor)
{
if (layer)
layer.contentsScale = newScaleFactor;
}
//-----------------------------------------------------------------------------
void CAViewLayer::drawLayer (void* cgContext)
{
CGContextRef ctx = reinterpret_cast<CGContextRef> (cgContext);
#if DEBUG
static bool visualizeLayer = false;
if (visualizeLayer)
CGContextClearRect (ctx, [layer bounds]);
#endif
CGRect dirtyRect = CGContextGetClipBoundingBox (ctx);
if ([layer contentsAreFlipped] == [layer isGeometryFlipped])
{
CGContextScaleCTM (ctx, 1, -1);
CGContextTranslateCTM (ctx, 0, -[layer bounds].size.height);
dirtyRect.origin.y =
(-dirtyRect.origin.y - dirtyRect.size.height) + [layer bounds].size.height;
}
CGContextSaveGState (ctx);
auto device = getPlatformFactory ().getGraphicsDeviceFactory ().getDeviceForScreen (
DefaultScreenIdentifier);
if (!device)
return;
auto cgDevice = std::static_pointer_cast<CoreGraphicsDevice> (device);
if (auto deviceContext =
std::make_shared<CoreGraphicsDeviceContext> (*cgDevice.get (), cgContext))
{
deviceContext->beginDraw ();
drawDelegate->drawViewLayerRects (deviceContext, layer.contentsScale,
{1, CRectFromCGRect (dirtyRect)});
deviceContext->endDraw ();
}
CGContextRestoreGState (ctx);
#if DEBUG
if (visualizeLayer)
{
CGContextSetRGBFillColor (ctx, 1., 0., 0., 0.3);
CGContextFillRect (ctx, [layer bounds]);
CGContextSetRGBFillColor (ctx, 0., 1., 0., 0.3);
CGContextFillRect (ctx, dirtyRect);
}
#endif
}
} // VSTGUI
#endif // MAC_COCOA
@@ -0,0 +1,65 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformfont.h"
#include "../platformfactory.h"
#if MAC
#include "../../ccolor.h"
#if TARGET_OS_IPHONE
#include <CoreText/CoreText.h>
#else
#include <ApplicationServices/ApplicationServices.h>
#endif
namespace VSTGUI {
class MacString;
//-----------------------------------------------------------------------------
class CoreTextFont : public IPlatformFont, public IFontPainter
{
public:
CoreTextFont (const UTF8String& name, const CCoord& size, const int32_t& style);
double getAscent () const override;
double getDescent () const override;
double getLeading () const override;
double getCapHeight () const override;
const IFontPainter* getPainter () const override { return this; }
CTFontRef getFontRef () const { return fontRef; }
CGFloat getSize () const { return CTFontGetSize (fontRef); }
static bool getAllFontFamilies (const FontFamilyCallback& callback) noexcept;
//------------------------------------------------------------------------------------
protected:
~CoreTextFont () noexcept override;
void drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string,
const CPoint& p, const CColor& color, bool antialias = true) const override;
CCoord getStringWidth (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string,
bool antialias = true) const override;
CFDictionaryRef getStringAttributes (const CGColorRef color = nullptr) const;
CTLineRef createCTLine (const PlatformGraphicsDeviceContextPtr& context, MacString* macString,
const CColor& color) const;
CTFontRef fontRef;
int32_t style;
bool underlineStyle;
mutable CColor lastColor;
mutable CFMutableDictionaryRef stringAttributes;
double ascent;
double descent;
double leading;
double capHeight;
};
} // VSTGUI
#endif // MAC
@@ -0,0 +1,328 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "cfontmac.h"
#if MAC
#import "macstring.h"
#import "coregraphicsdevicecontext.h"
#import "macglobals.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#endif
#ifndef MAC_OS_X_VERSION_10_14
#define MAC_OS_X_VERSION_10_14 101400
#endif
namespace VSTGUI {
//-----------------------------------------------------------------------------
struct RegisterBundleFonts
{
static void init ()
{
static RegisterBundleFonts instance;
}
private:
static void ErrorApplierFunction (const void *value, void *context)
{
auto error = CFErrorRef (value);
CFShow (error);
}
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_14
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
RegisterBundleFonts ()
{
fontUrls = CFArrayCreateMutable (kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
auto fontTypes = {CFSTR ("ttf"), CFSTR ("ttc"), CFSTR ("otf")};
for (auto& t : fontTypes)
getUrlsForType (t, fontUrls);
if (CFArrayGetCount (fontUrls) == 0)
return;
#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_14
CTFontManagerRegisterFontURLs (
fontUrls, kCTFontManagerScopeProcess, true, [] (CFArrayRef errors, bool done) {
CFArrayApplyFunction (errors, CFRangeMake (0, CFArrayGetCount (errors)),
ErrorApplierFunction, nullptr);
return true;
});
#else
CFArrayRef errors;
if (!CTFontManagerRegisterFontsForURLs (fontUrls, kCTFontManagerScopeProcess, &errors))
{
CFArrayApplyFunction (errors, CFRangeMake (0, CFArrayGetCount (errors)),
ErrorApplierFunction, nullptr);
CFRelease (errors);
}
#endif
}
~RegisterBundleFonts ()
{
if (CFArrayGetCount (fontUrls) == 0)
return;
#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_14
CTFontManagerUnregisterFontURLs (
fontUrls, kCTFontManagerScopeProcess, [] (CFArrayRef errors, bool done) {
CFArrayApplyFunction (errors, CFRangeMake (0, CFArrayGetCount (errors)),
ErrorApplierFunction, nullptr);
return true;
});
#else
CFArrayRef errors;
if (!CTFontManagerUnregisterFontsForURLs (fontUrls, kCTFontManagerScopeProcess, &errors))
{
CFArrayApplyFunction (errors, CFRangeMake (0, CFArrayGetCount (errors)),
ErrorApplierFunction, nullptr);
CFRelease (errors);
}
#endif
}
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_14
#pragma clang diagnostic pop
#endif
void getUrlsForType (CFStringRef fontType, CFMutableArrayRef& array)
{
if (auto a = CFBundleCopyResourceURLsOfType (getBundleRef (), fontType, CFSTR ("Fonts")))
{
CFArrayAppendArray (array, a, CFRangeMake (0, CFArrayGetCount (a)));
CFRelease (a);
}
}
CFMutableArrayRef fontUrls;
};
//-----------------------------------------------------------------------------
bool CoreTextFont::getAllFontFamilies (const FontFamilyCallback& callback) noexcept
{
RegisterBundleFonts::init ();
#if TARGET_OS_IPHONE
NSArray* fonts = [UIFont familyNames];
#else
NSArray* fonts = [(NSArray*)CTFontManagerCopyAvailableFontFamilyNames () autorelease];
#endif
fonts = [fonts sortedArrayUsingSelector:@selector (localizedCaseInsensitiveCompare:)];
for (uint32_t i = 0; i < [fonts count]; i++)
{
NSString* font = [fonts objectAtIndex:i];
if (!callback ([font UTF8String]))
break;
}
return true;
}
//-----------------------------------------------------------------------------
static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicTraits trait)
{
auto traitsFontRef = CTFontCreateCopyWithSymbolicTraits (fontRef, CTFontGetSize (fontRef),
nullptr, trait, trait);
if (traitsFontRef)
{
CFRelease (fontRef);
return traitsFontRef;
}
else if (trait == kCTFontItalicTrait)
{
CGAffineTransform transform = { 1, 0, -0.5, 1, 0, 0 };
traitsFontRef =
CTFontCreateCopyWithAttributes (fontRef, CTFontGetSize (fontRef), &transform, nullptr);
if (traitsFontRef)
{
CFRelease (fontRef);
return traitsFontRef;
}
}
return fontRef;
}
//-----------------------------------------------------------------------------
CoreTextFont::CoreTextFont (const UTF8String& name, const CCoord& size, const int32_t& style)
: fontRef (nullptr)
, style (style)
, underlineStyle (false)
, lastColor (MakeCColor (0,0,0,0))
, stringAttributes (nullptr)
, ascent (0.)
, descent (0.)
, leading (0.)
, capHeight (0.)
{
RegisterBundleFonts::init ();
CFStringRef fontNameRef = fromUTF8String<CFStringRef> (name);
if (fontNameRef)
{
if (@available (macOS 10.10, *))
{
auto attributes =
CFDictionaryCreateMutable (kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue (attributes, kCTFontFamilyNameAttribute, fontNameRef);
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes (attributes);
fontRef = CTFontCreateWithFontDescriptor (descriptor, static_cast<CGFloat> (size), nullptr);
CFRelease (attributes);
CFRelease (descriptor);
}
else
{
fontRef = CTFontCreateWithName (fontNameRef, static_cast<CGFloat> (size), nullptr);
}
if (style & kBoldFace)
fontRef = CoreTextCreateTraitsVariant (fontRef, kCTFontBoldTrait);
if (style & kItalicFace)
fontRef = CoreTextCreateTraitsVariant (fontRef, kCTFontItalicTrait);
if (fontRef)
{
ascent = CTFontGetAscent (fontRef);
descent = CTFontGetDescent (fontRef);
leading = CTFontGetLeading (fontRef);
capHeight = CTFontGetCapHeight (fontRef);
}
}
}
//-----------------------------------------------------------------------------
CoreTextFont::~CoreTextFont () noexcept
{
if (stringAttributes)
CFRelease (stringAttributes);
if (fontRef)
CFRelease (fontRef);
}
//-----------------------------------------------------------------------------
double CoreTextFont::getAscent () const
{
return ascent;
}
//-----------------------------------------------------------------------------
double CoreTextFont::getDescent () const
{
return descent;
}
//-----------------------------------------------------------------------------
double CoreTextFont::getLeading () const
{
return leading;
}
//-----------------------------------------------------------------------------
double CoreTextFont::getCapHeight () const
{
return capHeight;
}
//-----------------------------------------------------------------------------
CFDictionaryRef CoreTextFont::getStringAttributes (const CGColorRef color) const
{
if (stringAttributes == nullptr)
{
stringAttributes =
CFDictionaryCreateMutable (kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue (stringAttributes, kCTFontAttributeName, fontRef);
}
if (color)
{
CFDictionarySetValue (stringAttributes, kCTForegroundColorAttributeName, color);
}
return stringAttributes;
}
//-----------------------------------------------------------------------------
CTLineRef CoreTextFont::createCTLine (const PlatformGraphicsDeviceContextPtr& context,
MacString* macString, const CColor& color) const
{
if (macString->getCTLineFontRef () == this && macString->getCTLineColor () == color)
{
CTLineRef line = macString->getCTLine ();
CFRetain (line);
return line;
}
CFStringRef cfStr = macString->getCFString ();
if (cfStr == nullptr)
{
#if DEBUG
DebugPrint ("Empty CFStringRef in MacString. This is unexpected !\n");
#endif
return nullptr;
}
CGColorRef cgColorRef = nullptr;
if (color != lastColor)
{
cgColorRef = getCGColor (color);
lastColor = color;
}
if (auto attrStr =
CFAttributedStringCreate (kCFAllocatorDefault, cfStr, getStringAttributes (cgColorRef)))
{
CTLineRef line = CTLineCreateWithAttributedString (attrStr);
if (context && line)
{
macString->setCTLine (line, this, color);
}
CFRelease (attrStr);
return line;
}
return nullptr;
}
//-----------------------------------------------------------------------------
void CoreTextFont::drawString (const PlatformGraphicsDeviceContextPtr& context,
IPlatformString* string, const CPoint& point, const CColor& color,
bool antialias) const
{
MacString* macString = dynamic_cast<MacString*> (string);
if (macString == nullptr)
return;
auto deviceContext = std::dynamic_pointer_cast<CoreGraphicsDeviceContext> (context);
if (!deviceContext)
return;
CTLineRef line = createCTLine (context, macString, color);
if (!line)
return;
CGPoint cgPoint = CGPointFromCPoint (point);
deviceContext->drawCTLine (line, cgPoint, fontRef, color, style & kUnderlineFace,
style & kStrikethroughFace, antialias);
CFRelease (line);
}
//-----------------------------------------------------------------------------
CCoord CoreTextFont::getStringWidth (const PlatformGraphicsDeviceContextPtr& context,
IPlatformString* string, bool antialias) const
{
CCoord result = 0;
MacString* macString = dynamic_cast<MacString*> (string);
if (macString == nullptr)
return result;
CTLineRef line = createCTLine (context, macString, kBlackCColor);
if (line)
{
result = CTLineGetTypographicBounds (line, nullptr, nullptr, nullptr);
CFRelease (line);
}
return result;
}
} // VSTGUI
#endif // MAC
@@ -0,0 +1,452 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cgbitmap.h"
#include "../../cresourcedescription.h"
#if MAC
#include "macglobals.h"
#include <Accelerate/Accelerate.h>
#include <AssertMacros.h>
#if TARGET_OS_IPHONE
#include <MobileCoreServices/MobileCoreServices.h>
#endif
namespace VSTGUI {
//-----------------------------------------------------------------------------
PlatformBitmapPtr CGBitmap::create (CPoint* size)
{
if (size)
return makeOwned<CGBitmap> (*size);
return makeOwned<CGBitmap> ();
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr CGBitmap::createFromPath (UTF8StringPtr absolutePath)
{
PlatformBitmapPtr bitmap;
CFURLRef url = CFURLCreateFromFileSystemRepresentation (nullptr, (const UInt8*)absolutePath, static_cast<CFIndex> (strlen (absolutePath)), false);
if (url)
{
CGImageSourceRef source = CGImageSourceCreateWithURL (url, nullptr);
if (source)
{
auto cgBitmap = makeOwned<CGBitmap> ();
bool result = cgBitmap->loadFromImageSource (source);
if (result)
bitmap = std::move (cgBitmap);
CFRelease (source);
}
CFRelease (url);
}
return bitmap;
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr CGBitmap::createFromMemory (const void* ptr, uint32_t memSize)
{
PlatformBitmapPtr bitmap;
CFDataRef data = CFDataCreate (nullptr, (const UInt8*)ptr, static_cast<CFIndex> (memSize));
if (data)
{
CGImageSourceRef source = CGImageSourceCreateWithData (data, nullptr);
if (source)
{
auto cgBitmap = makeOwned<CGBitmap> ();
bool result = cgBitmap->loadFromImageSource (source);
if (result)
bitmap = std::move (cgBitmap);
CFRelease (source);
}
CFRelease (data);
}
return bitmap;
}
//-----------------------------------------------------------------------------
PNGBitmapBuffer CGBitmap::createMemoryPNGRepresentation (const PlatformBitmapPtr& bitmap)
{
PNGBitmapBuffer buffer;
#if !TARGET_OS_IPHONE
if (auto cgBitmap = bitmap.cast<CGBitmap> ())
{
CGImageRef image = cgBitmap->getCGImage ();
if (image)
{
CFMutableDataRef data = CFDataCreateMutable (nullptr, 0);
if (data)
{
CGImageDestinationRef dest = CGImageDestinationCreateWithData (data, CFSTR ("public.png"), 1, nullptr);
if (dest)
{
auto scaleFactor = bitmap->getScaleFactor ();
CFMutableDictionaryRef properties = nullptr;
if (scaleFactor != 1.)
{
properties = CFDictionaryCreateMutable (nullptr, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
double dpi = 72 * scaleFactor;
auto number = CFNumberCreate(nullptr, kCFNumberDoubleType, &dpi);
CFDictionaryAddValue (properties, kCGImagePropertyDPIWidth, number);
CFDictionaryAddValue (properties, kCGImagePropertyDPIHeight, number);
CFRelease (number);
}
CGImageDestinationAddImage (dest, image, properties);
if (CGImageDestinationFinalize (dest))
{
buffer.resize(CFDataGetLength (data));
CFDataGetBytes (data, CFRangeMake (0, CFDataGetLength (data)), buffer.data ());
}
if (properties)
CFRelease (properties);
CFRelease (dest);
}
CFRelease (data);
}
}
}
#endif
return buffer;
}
//-----------------------------------------------------------------------------
CGBitmap::CGBitmap (const CPoint& inSize)
: size (inSize)
{
allocBits ();
}
//-----------------------------------------------------------------------------
CGBitmap::CGBitmap (CGImageRef image)
: image (image)
{
CGImageRetain (image);
size.x = CGImageGetWidth (image);
size.y = CGImageGetHeight (image);
}
//-----------------------------------------------------------------------------
CGBitmap::CGBitmap ()
{
}
//-----------------------------------------------------------------------------
CGBitmap::~CGBitmap () noexcept
{
if (image)
CGImageRelease (image);
if (layer)
CFRelease (layer);
if (imageSource)
CFRelease (imageSource);
if (bitsDataProvider)
CFRelease (bitsDataProvider);
}
//-----------------------------------------------------------------------------
bool CGBitmap::load (const CResourceDescription& desc)
{
if (bits)
return false;
bool result = false;
if (getBundleRef ())
{
// find the bitmap in our Bundle.
// If the resource description is of type integer, it must be in the form of bmp00123.png, where the resource id would be 123.
// else it just uses the name
char filename [PATH_MAX];
if (desc.type == CResourceDescription::kIntegerType)
snprintf (filename, PATH_MAX, "bmp%05d", (int32_t)desc.u.id);
else
std::strcpy (filename, desc.u.name);
CFStringRef cfStr = CFStringCreateWithCString (nullptr, filename, kCFStringEncodingUTF8);
if (cfStr)
{
CFURLRef url = nullptr;
if (filename[0] == '/')
url = CFURLCreateFromFileSystemRepresentation (nullptr, (const UInt8*)filename, static_cast<CFIndex> (strlen (filename)), false);
int32_t i = 0;
while (url == nullptr)
{
static CFStringRef resTypes [] = { CFSTR("png"), CFSTR("bmp"), CFSTR("jpg"), CFSTR("pict"), nullptr };
url = CFBundleCopyResourceURL (getBundleRef (), cfStr, desc.type == CResourceDescription::kIntegerType ? resTypes[i] : nullptr, nullptr);
if (resTypes[++i] == nullptr)
break;
}
CFRelease (cfStr);
if (url)
{
CGImageSourceRef source = CGImageSourceCreateWithURL (url, nullptr);
if (source)
{
result = loadFromImageSource (source);
CFRelease (source);
}
CFRelease (url);
}
}
}
#if DEBUG
if (result == false)
{
if (desc.type == CResourceDescription::kIntegerType)
DebugPrint ("*** Bitmap Nr.:%d not found.\n", desc.u.id);
else
DebugPrint ("*** Bitmap '%s' not found.\n", desc.u.name);
}
#endif
return result;
}
//-----------------------------------------------------------------------------
static CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32");
#define VSTGUI_QUARTZ_WORKAROUND_PNG_DECODE_ON_DRAW_BUG __i386__ || TARGET_OS_IPHONE
//-----------------------------------------------------------------------------
bool CGBitmap::loadFromImageSource (CGImageSourceRef source)
{
imageSource = source;
if (imageSource)
{
CFRetain (imageSource);
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex (imageSource, 0, nullptr);
if (properties == nullptr)
{
return false;
}
CFNumberRef value = (CFNumberRef)CFDictionaryGetValue (properties, kCGImagePropertyPixelHeight);
if (value)
{
double fValue = 0;
if (CFNumberGetValue (value, kCFNumberDoubleType, &fValue))
size.y = fValue;
}
value = (CFNumberRef)CFDictionaryGetValue (properties, kCGImagePropertyPixelWidth);
if (value)
{
double fValue = 0;
if (CFNumberGetValue (value, kCFNumberDoubleType, &fValue))
size.x = fValue;
}
#if VSTGUI_QUARTZ_WORKAROUND_PNG_DECODE_ON_DRAW_BUG
// workaround a bug in Mac OS X 10.6 (32 bit), where PNG bitmaps were decoded all the time when drawn.
// we fix this by copying the pixels of the bitmap into our own buffer.
CFStringRef imageType = CGImageSourceGetType (imageSource);
if (imageType && CFStringCompare (imageType, kUTTypePNG, 0) == kCFCompareEqualTo)
{
CGContextRef context = createCGContext ();
if (context)
{
dirty = true;
CFRelease (context);
}
}
#endif
CFRelease (properties);
}
return (size.x != 0 && size.y != 0);
}
//-----------------------------------------------------------------------------
CGImageRef CGBitmap::getCGImage ()
{
if (image == nullptr && imageSource)
{
const void* keys[] = {kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32};
const void* values[] = {kCFBooleanTrue, kCFBooleanTrue};
CFDictionaryRef options = CFDictionaryCreate (nullptr, keys, values, 2, nullptr, nullptr);
image = CGImageSourceCreateImageAtIndex (imageSource, 0, options);
CFRelease (imageSource);
CFRelease (options);
imageSource = nullptr;
}
if ((dirty || image == nullptr) && bits)
{
freeCGImage ();
size_t rowBytes = getBytesPerRow ();
size_t bitDepth = 32;
CGBitmapInfo bitmapInfo =
static_cast<CGBitmapInfo> (kCGImageAlphaPremultipliedFirst) | kCGBitmapByteOrder32Big;
image = CGImageCreate (static_cast<size_t> (size.x), static_cast<size_t> (size.y), 8, bitDepth, rowBytes, GetCGColorSpace (), bitmapInfo, bitsDataProvider, nullptr, false, kCGRenderingIntentDefault);
dirty = false;
}
return image;
}
//-----------------------------------------------------------------------------
CGContextRef CGBitmap::createCGContext ()
{
CGContextRef context = nullptr;
if (bits == nullptr)
{
allocBits ();
if (imageSource)
getCGImage ();
if (image)
{
context = createCGContext ();
if (context)
{
CGContextScaleCTM (context, 1, -1);
CGContextDrawImage (context, CGRectMake (0, static_cast<CGFloat> (-size.y), static_cast<CGFloat> (size.x), static_cast<CGFloat> (size.y)), image);
CGContextScaleCTM (context, 1, -1);
return context;
}
}
}
if (bits)
{
CGBitmapInfo bitmapInfo =
static_cast<CGBitmapInfo> (kCGImageAlphaPremultipliedFirst) | kCGBitmapByteOrder32Big;
context = CGBitmapContextCreate (bits,
static_cast<size_t> (size.x),
static_cast<size_t> (size.y),
8,
getBytesPerRow (),
GetCGColorSpace (),
bitmapInfo);
CGContextTranslateCTM (context, 0, (CGFloat)size.y);
CGContextScaleCTM (context, 1, -1);
}
return context;
}
//-----------------------------------------------------------------------------
CGLayerRef CGBitmap::createCGLayer (CGContextRef context)
{
if (layer && !dirty)
return layer;
CGImageRef cgImage = getCGImage ();
layer = cgImage ? CGLayerCreateWithContext (context, CGSizeFromCPoint (size), nullptr) : nullptr;
if (layer)
{
CGContextRef layerContext = CGLayerGetContext (layer);
CGContextDrawImage (layerContext, CGRectMake (0, 0, static_cast<CGFloat> (size.x), static_cast<CGFloat> (size.y)), cgImage);
}
return layer;
}
//-----------------------------------------------------------------------------
void CGBitmap::allocBits ()
{
if (bits == nullptr)
{
bytesPerRow = static_cast<uint32_t> (size.x * 4);
if (bytesPerRow % 16)
bytesPerRow += 16 - (bytesPerRow % 16);
uint32_t bitmapByteCount = bytesPerRow * static_cast<uint32_t> (size.y);
bits = calloc (1, bitmapByteCount);
bitsDataProvider = CGDataProviderCreateWithData (
nullptr, bits, bitmapByteCount,
[] (void* __nullable info, const void* data, size_t) {
std::free (const_cast<void*> (data));
});
}
}
//-----------------------------------------------------------------------------
void CGBitmap::freeCGImage ()
{
if (image)
CFRelease (image);
image = nullptr;
if (layer)
CFRelease (layer);
layer = nullptr;
}
//-----------------------------------------------------------------------------
class CGBitmapPixelAccess : public IPlatformBitmapPixelAccess
{
public:
CGBitmapPixelAccess (CGBitmap* bitmap, bool alphaPremultiplied)
: bitmap (bitmap)
, alphaPremultiplied (alphaPremultiplied)
{
if (!alphaPremultiplied)
{
vImage_Buffer buffer;
buffer.data = bitmap->getBits ();
buffer.width = static_cast<vImagePixelCount> (bitmap->getSize ().x);
buffer.height = static_cast<vImagePixelCount> (bitmap->getSize ().y);
buffer.rowBytes = bitmap->getBytesPerRow ();
#if DEBUG
vImage_Error error =
#endif
vImageUnpremultiplyData_ARGB8888 (&buffer, &buffer, kvImageNoFlags);
#if DEBUG
assert (error == kvImageNoError);
#endif
}
bitmap->remember ();
}
~CGBitmapPixelAccess () noexcept override
{
if (!alphaPremultiplied)
{
vImage_Buffer buffer;
buffer.data = bitmap->getBits ();
buffer.width = static_cast<vImagePixelCount> (bitmap->getSize ().x);
buffer.height = static_cast<vImagePixelCount> (bitmap->getSize ().y);
buffer.rowBytes = bitmap->getBytesPerRow ();
#if DEBUG
vImage_Error error =
#endif
vImagePremultiplyData_ARGB8888 (&buffer, &buffer, kvImageNoFlags);
#if DEBUG
assert (error == kvImageNoError);
#endif
}
bitmap->setDirty ();
bitmap->forget ();
}
uint8_t* getAddress () const override
{
return (uint8_t*)bitmap->getBits ();
}
uint32_t getBytesPerRow () const override
{
return bitmap->getBytesPerRow ();
}
PixelFormat getPixelFormat () const override
{
#ifdef __BIG_ENDIAN__
return kRGBA;
#else
return kARGB;
#endif
}
protected:
CGBitmap* bitmap;
bool alphaPremultiplied;
};
//-----------------------------------------------------------------------------
SharedPointer<IPlatformBitmapPixelAccess> CGBitmap::lockPixels (bool alphaPremultiplied)
{
if (bits == nullptr)
{
CGContextRef context = createCGContext ();
if (context)
CFRelease (context);
}
if (bits)
{
return makeOwned<CGBitmapPixelAccess> (this, alphaPremultiplied);
}
return nullptr;
}
} // VSTGUI
#endif // MAC
@@ -0,0 +1,73 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformbitmap.h"
#include "../platformfwd.h"
#if MAC
#include "../../cpoint.h"
#if TARGET_OS_IPHONE
#include <CoreGraphics/CoreGraphics.h>
#include <ImageIO/ImageIO.h>
#else
#include <ApplicationServices/ApplicationServices.h>
#endif
namespace VSTGUI {
//-----------------------------------------------------------------------------
class CGBitmap : public IPlatformBitmap
{
public:
static PlatformBitmapPtr create (CPoint* size);
static PlatformBitmapPtr createFromPath (UTF8StringPtr absolutePath);
static PlatformBitmapPtr createFromMemory (const void* ptr, uint32_t memSize);
static PNGBitmapBuffer createMemoryPNGRepresentation (const PlatformBitmapPtr& bitmap);
explicit CGBitmap (const CPoint& size);
explicit CGBitmap (CGImageRef image);
CGBitmap ();
~CGBitmap () noexcept override;
bool load (const CResourceDescription& desc);
const CPoint& getSize () const override { return size; }
SharedPointer<IPlatformBitmapPixelAccess> lockPixels (bool alphaPremultiplied) override;
void setScaleFactor (double factor) override { scaleFactor = factor; }
double getScaleFactor () const override { return scaleFactor; }
CGImageRef getCGImage ();
CGContextRef createCGContext ();
bool loadFromImageSource (CGImageSourceRef source);
void setDirty () { dirty = true; }
void* getBits () const { return bits; }
uint32_t getBytesPerRow () const { return bytesPerRow; }
CGLayerRef createCGLayer (CGContextRef context);
CGLayerRef getCGLayer () const { return layer; }
//-----------------------------------------------------------------------------
protected:
void allocBits ();
void freeCGImage ();
CPoint size;
CGImageRef image {nullptr};
CGImageSourceRef imageSource {nullptr};
CGLayerRef layer {nullptr};
CGDataProviderRef bitsDataProvider {nullptr};
void* bits {nullptr};
bool dirty {false};
uint32_t bytesPerRow {0};
double scaleFactor {1.};
};
} // VSTGUI
#endif // MAC
@@ -0,0 +1,33 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../vstguibase.h"
#if MAC
#ifdef __OBJC__
@class NSAutoreleasePool;
#else
struct NSAutoreleasePool;
#endif // __OBJC__
namespace VSTGUI {
//------------------------------------------------------------------------------------
class AutoreleasePool
{
public:
AutoreleasePool ();
~AutoreleasePool () noexcept;
//------------------------------------------------------------------------------------
protected:
NSAutoreleasePool* pool;
};
} // VSTGUI
#endif // MAC
@@ -0,0 +1,27 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "autoreleasepool.h"
#if MAC
#import <Foundation/Foundation.h>
namespace VSTGUI {
//-----------------------------------------------------------------------------
AutoreleasePool::AutoreleasePool ()
{
pool = [[NSAutoreleasePool alloc] init];
}
//-----------------------------------------------------------------------------
AutoreleasePool::~AutoreleasePool () noexcept
{
[pool drain];
}
} // VSTGUI
#endif // MAC
@@ -0,0 +1,131 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#import "../../../crect.h"
#import "../../../cpoint.h"
#import "../../../ccolor.h"
#import "../../../vstguifwd.h"
#if MAC_COCOA && defined (__OBJC__)
#import <objc/runtime.h>
#import <objc/message.h>
#import <Cocoa/Cocoa.h>
#define HIDDEN __attribute__((__visibility__("hidden")))
//------------------------------------------------------------------------------------
extern HIDDEN bool CreateKeyboardEventFromNSEvent (NSEvent* theEvent, VSTGUI::KeyboardEvent& event);
extern HIDDEN NSString* GetVirtualKeyCodeString (VSTGUI::VirtualKey virtualKey);
extern HIDDEN int32_t eventButton (NSEvent* theEvent);
extern HIDDEN void convertPointToGlobal (NSView* view, NSPoint& p);
extern HIDDEN NSImage* bitmapToNSImage (VSTGUI::CBitmap* bitmap);
//------------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------------
HIDDEN inline NSRect nsRectFromCRect (const VSTGUI::CRect& rect)
{
NSRect r;
r.origin.x = static_cast<CGFloat> (rect.left);
r.origin.y = static_cast<CGFloat> (rect.top);
r.size.width = static_cast<CGFloat> (rect.getWidth ());
r.size.height = static_cast<CGFloat> (rect.getHeight ());
return r;
}
//------------------------------------------------------------------------------------
HIDDEN inline NSPoint nsPointFromCPoint (const VSTGUI::CPoint& point)
{
NSPoint p = { static_cast<CGFloat>(point.x), static_cast<CGFloat>(point.y) };
return p;
}
//------------------------------------------------------------------------------------
HIDDEN inline VSTGUI::CRect rectFromNSRect (const NSRect& rect)
{
VSTGUI::CRect r (rect.origin.x, rect.origin.y, 0, 0);
r.setWidth (rect.size.width);
r.setHeight (rect.size.height);
return r;
}
//------------------------------------------------------------------------------------
HIDDEN inline VSTGUI::CPoint pointFromNSPoint (const NSPoint& point)
{
VSTGUI::CPoint p (point.x, point.y);
return p;
}
//------------------------------------------------------------------------------------
HIDDEN inline NSColor* nsColorFromCColor (const VSTGUI::CColor& color)
{
return [NSColor colorWithDeviceRed:color.normRed<CGFloat> ()
green:color.normGreen<CGFloat> ()
blue:color.normBlue<CGFloat> ()
alpha:color.normAlpha<CGFloat> ()];
}
//------------------------------------------------------------------------------------
struct MacEventModifier
{
enum mask
{
#ifdef MAC_OS_X_VERSION_10_12
ShiftKeyMask = NSEventModifierFlagShift,
CommandKeyMask = NSEventModifierFlagCommand,
AlternateKeyMask = NSEventModifierFlagOption,
ControlKeyMask = NSEventModifierFlagControl
#else
ShiftKeyMask = NSShiftKeyMask,
CommandKeyMask = NSCommandKeyMask,
AlternateKeyMask = NSAlternateKeyMask,
ControlKeyMask = NSControlKeyMask
#endif
};
};
//------------------------------------------------------------------------------------
namespace MacEventType
{
#ifdef MAC_OS_X_VERSION_10_12
static constexpr auto LeftMouseDown = ::NSEventTypeLeftMouseDown;
static constexpr auto LeftMouseDragged = ::NSEventTypeLeftMouseDragged;
static constexpr auto MouseMoved = ::NSEventTypeMouseMoved;
static constexpr auto KeyDown = ::NSEventTypeKeyDown;
static constexpr auto KeyUp = ::NSEventTypeKeyUp;
#else
static constexpr auto LeftMouseDown = ::NSLeftMouseDown;
static constexpr auto LeftMouseDragged = ::NSLeftMouseDragged;
static constexpr auto MouseMoved = ::NSMouseMoved;
static constexpr auto KeyDown = ::NSKeyDown;
static constexpr auto KeyUp = ::NSKeyUp;
#endif
}
//------------------------------------------------------------------------------------
namespace MacWindowStyleMask
{
#ifdef MAC_OS_X_VERSION_10_12
static constexpr auto Borderless = ::NSWindowStyleMaskBorderless;
static constexpr auto Titled = ::NSWindowStyleMaskTitled;
static constexpr auto Resizable = ::NSWindowStyleMaskResizable;
static constexpr auto Miniaturizable = ::NSWindowStyleMaskMiniaturizable;
static constexpr auto Closable = ::NSWindowStyleMaskClosable;
static constexpr auto Utility = ::NSWindowStyleMaskUtilityWindow;
static constexpr auto FullSizeContentView = ::NSWindowStyleMaskFullSizeContentView;
#else
static constexpr auto Borderless = ::NSBorderlessWindowMask;
static constexpr auto Titled = ::NSTitledWindowMask;
static constexpr auto Resizable = ::NSResizableWindowMask;
static constexpr auto Miniaturizable = ::NSMiniaturizableWindowMask;
static constexpr auto Closable = ::NSClosableWindowMask;
static constexpr auto Utility = ::NSUtilityWindowMask;
static constexpr auto FullSizeContentView = ::NSFullSizeContentViewWindowMask;
#endif
}
#endif // MAC_COCOA
@@ -0,0 +1,259 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cocoahelpers.h"
#if MAC_COCOA
#include "../../../vstkeycode.h"
#include "../../../events.h"
#include "../../../cview.h"
#include "../../../cbitmap.h"
#include "../cgbitmap.h"
using namespace VSTGUI;
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
HIDDEN bool CreateKeyboardEventFromNSEvent (NSEvent* theEvent, KeyboardEvent& event)
{
if (theEvent.type == NSEventTypeKeyUp)
event.type = EventType::KeyUp;
else if (theEvent.type == NSEventTypeKeyDown || theEvent.type == NSEventTypeFlagsChanged)
{
event.type = EventType::KeyDown;
if (theEvent.ARepeat)
event.isRepeat = true;
}
else
return false;
NSString *s = [theEvent charactersIgnoringModifiers];
if ([s length] == 1)
{
char32_t utf32Char = {};
if (![s getBytes:&utf32Char
maxLength:sizeof (utf32Char)
usedLength:nullptr
encoding:NSUTF32StringEncoding
options:0
range:NSMakeRange (0, 1)
remainingRange:nullptr])
{
utf32Char = [s characterAtIndex:0];
}
switch (utf32Char)
{
case 8: case 0x7f: event.virt = VirtualKey::Back; break;
case 9: case 0x19: event.virt = VirtualKey::Tab; break;
case NSClearLineFunctionKey: event.virt = VirtualKey::Clear; break;
case 0xd: event.virt = VirtualKey::Return; break;
case NSPauseFunctionKey: event.virt = VirtualKey::Pause; break;
case 0x1b: event.virt = VirtualKey::Escape; break;
case ' ': event.virt = VirtualKey::Space; break;
case NSNextFunctionKey: event.virt = VirtualKey::Next; break;
case NSEndFunctionKey: event.virt = VirtualKey::End; break;
case NSHomeFunctionKey: event.virt = VirtualKey::Home; break;
case NSLeftArrowFunctionKey: event.virt = VirtualKey::Left; break;
case NSUpArrowFunctionKey: event.virt = VirtualKey::Up; break;
case NSRightArrowFunctionKey: event.virt = VirtualKey::Right; break;
case NSDownArrowFunctionKey: event.virt = VirtualKey::Down; break;
case NSPageUpFunctionKey: event.virt = VirtualKey::PageUp; break;
case NSPageDownFunctionKey: event.virt = VirtualKey::PageDown; break;
case NSSelectFunctionKey: event.virt = VirtualKey::Select; break;
case NSPrintFunctionKey: event.virt = VirtualKey::Print; break;
// VirtualKey::ENTER
// VirtualKey::SNAPSHOT
case NSInsertFunctionKey: event.virt = VirtualKey::Insert; break;
case NSDeleteFunctionKey: event.virt = VirtualKey::Delete; break;
case NSHelpFunctionKey: event.virt = VirtualKey::Help; break;
case NSF1FunctionKey: event.virt = VirtualKey::F1; break;
case NSF2FunctionKey: event.virt = VirtualKey::F2; break;
case NSF3FunctionKey: event.virt = VirtualKey::F3; break;
case NSF4FunctionKey: event.virt = VirtualKey::F4; break;
case NSF5FunctionKey: event.virt = VirtualKey::F5; break;
case NSF6FunctionKey: event.virt = VirtualKey::F6; break;
case NSF7FunctionKey: event.virt = VirtualKey::F7; break;
case NSF8FunctionKey: event.virt = VirtualKey::F8; break;
case NSF9FunctionKey: event.virt = VirtualKey::F9; break;
case NSF10FunctionKey: event.virt = VirtualKey::F10; break;
case NSF11FunctionKey: event.virt = VirtualKey::F11; break;
case NSF12FunctionKey: event.virt = VirtualKey::F12; break;
default:
{
switch ([theEvent keyCode])
{
case 82: event.virt = VirtualKey::NumPad0; break;
case 83: event.virt = VirtualKey::NumPad1; break;
case 84: event.virt = VirtualKey::NumPad2; break;
case 85: event.virt = VirtualKey::NumPad3; break;
case 86: event.virt = VirtualKey::NumPad4; break;
case 87: event.virt = VirtualKey::NumPad5; break;
case 88: event.virt = VirtualKey::NumPad6; break;
case 89: event.virt = VirtualKey::NumPad7; break;
case 91: event.virt = VirtualKey::NumPad8; break;
case 92: event.virt = VirtualKey::NumPad9; break;
case 67: event.virt = VirtualKey::Multiply; break;
case 69: event.virt = VirtualKey::Add; break;
case 78: event.virt = VirtualKey::Subtract; break;
case 65: event.virt = VirtualKey::Decimal; break;
case 75: event.virt = VirtualKey::Divide; break;
case 76: event.virt = VirtualKey::Enter; break;
default:
{
if ((utf32Char >= 'A') && (utf32Char <= 'Z'))
utf32Char += ('a' - 'A');
else
utf32Char = static_cast<char32_t> (tolower (utf32Char));
event.character = utf32Char;
break;
}
}
}
}
}
NSUInteger modifiers = [theEvent modifierFlags];
if (modifiers & MacEventModifier::ShiftKeyMask)
event.modifiers.add (ModifierKey::Shift);
if (modifiers & MacEventModifier::CommandKeyMask)
event.modifiers.add (ModifierKey::Control);
if (modifiers & MacEventModifier::AlternateKeyMask)
event.modifiers.add (ModifierKey::Alt);
if (modifiers & MacEventModifier::ControlKeyMask)
event.modifiers.add (ModifierKey::Super);
return true;
}
//------------------------------------------------------------------------------------
HIDDEN NSString* GetVirtualKeyCodeString (VirtualKey virtualKey)
{
unichar character = 0;
switch (virtualKey)
{
case VirtualKey::Back: character = NSBackspaceCharacter; break;
case VirtualKey::Tab: character = NSTabCharacter; break;
case VirtualKey::Clear: character = NSClearLineFunctionKey; break;
case VirtualKey::Return: character = NSCarriageReturnCharacter; break;
case VirtualKey::Pause: character = NSPauseFunctionKey; break;
case VirtualKey::Escape: character = 0x1b; break;
case VirtualKey::Space: character = ' '; break;
case VirtualKey::Next: character = NSNextFunctionKey; break;
case VirtualKey::End: character = NSEndFunctionKey; break;
case VirtualKey::Home: character = NSHomeFunctionKey; break;
case VirtualKey::Left: character = NSLeftArrowFunctionKey; break;
case VirtualKey::Up: character = NSUpArrowFunctionKey; break;
case VirtualKey::Right: character = NSRightArrowFunctionKey; break;
case VirtualKey::Down: character = NSDownArrowFunctionKey; break;
case VirtualKey::PageUp: character = NSPageUpFunctionKey; break;
case VirtualKey::PageDown: character = NSPageDownFunctionKey; break;
case VirtualKey::Select: character = NSSelectFunctionKey; break;
case VirtualKey::Print: character = NSPrintFunctionKey; break;
case VirtualKey::Enter: character = NSEnterCharacter; break;
case VirtualKey::Snapshot: break;
case VirtualKey::Insert: character = NSInsertFunctionKey; break;
case VirtualKey::Delete: character = NSDeleteFunctionKey; break;
case VirtualKey::Help: character = NSHelpFunctionKey; break;
case VirtualKey::NumPad0: break;
case VirtualKey::NumPad1: break;
case VirtualKey::NumPad2: break;
case VirtualKey::NumPad3: break;
case VirtualKey::NumPad4: break;
case VirtualKey::NumPad5: break;
case VirtualKey::NumPad6: break;
case VirtualKey::NumPad7: break;
case VirtualKey::NumPad8: break;
case VirtualKey::NumPad9: break;
case VirtualKey::Multiply: break;
case VirtualKey::Add: break;
case VirtualKey::Separator: break;
case VirtualKey::Subtract: break;
case VirtualKey::Decimal: break;
case VirtualKey::Divide: break;
case VirtualKey::F1: character = NSF1FunctionKey; break;
case VirtualKey::F2: character = NSF2FunctionKey; break;
case VirtualKey::F3: character = NSF3FunctionKey; break;
case VirtualKey::F4: character = NSF4FunctionKey; break;
case VirtualKey::F5: character = NSF5FunctionKey; break;
case VirtualKey::F6: character = NSF6FunctionKey; break;
case VirtualKey::F7: character = NSF7FunctionKey; break;
case VirtualKey::F8: character = NSF8FunctionKey; break;
case VirtualKey::F9: character = NSF9FunctionKey; break;
case VirtualKey::F10: character = NSF10FunctionKey; break;
case VirtualKey::F11: character = NSF11FunctionKey; break;
case VirtualKey::F12: character = NSF12FunctionKey; break;
case VirtualKey::NumLock: break;
case VirtualKey::Scroll: break;
case VirtualKey::Equals: break;
case VirtualKey::ShiftModifier: break;
case VirtualKey::ControlModifier: break;
case VirtualKey::AltModifier: break;
case VirtualKey::None: break;
}
if (character != 0)
return [NSString stringWithFormat:@"%C", character];
return nil;
}
//------------------------------------------------------------------------------------
HIDDEN int32_t eventButton (NSEvent* theEvent)
{
if ([theEvent type] == MacEventType::MouseMoved)
{
return 0;
}
int32_t buttons = 0;
switch ([theEvent buttonNumber])
{
case 0: buttons = ([theEvent modifierFlags] & MacEventModifier::ControlKeyMask) ? kRButton : kLButton; break;
case 1: buttons = kRButton; break;
case 2: buttons = kMButton; break;
case 3: buttons = kButton4; break;
case 4: buttons = kButton5; break;
}
return buttons;
}
//-----------------------------------------------------------------------------
HIDDEN void convertPointToGlobal (NSView* view, NSPoint& p)
{
p = [view convertPoint:p toView:nil];
if ([view window] == nil)
return;
NSRect r = {};
r.origin = p;
r = [[view window] convertRectToScreen:r];
p = r.origin;
}
//-----------------------------------------------------------------------------
HIDDEN NSImage* bitmapToNSImage (CBitmap* bitmap)
{
if (!bitmap)
return nil;
NSImage* image =
[[NSImage alloc] initWithSize:NSMakeSize (bitmap->getWidth (), bitmap->getHeight ())];
for (auto& platformBitmap : *bitmap)
{
if (auto cgBitmap = dynamic_cast<CGBitmap*> (platformBitmap.get ()))
{
if (auto rep = [[NSBitmapImageRep alloc] initWithCGImage:cgBitmap->getCGImage ()])
{
[image addRepresentation:rep];
[rep release];
}
}
}
return image;
}
#endif // MAC_COCOA
@@ -0,0 +1,57 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../iplatformopenglview.h"
#if VSTGUI_OPENGL_SUPPORT
#if MAC_COCOA
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
struct NSOpenGLView;
struct NSView;
#endif
namespace VSTGUI {
class NSViewFrame;
//-----------------------------------------------------------------------------
class CocoaOpenGLView : public IPlatformOpenGLView
{
public:
CocoaOpenGLView (NSView* parent);
~CocoaOpenGLView () noexcept override = default;
bool init (IOpenGLView* view, PixelFormat* pixelFormat = nullptr) override;
void remove () override;
void invalidRect (const CRect& rect) override;
void viewSizeChanged (const CRect& visibleSize) override;
bool makeContextCurrent () override;
bool lockContext () override;
bool unlockContext () override;
void swapBuffers () override;
void doDraw (const CRect& r);
void reshape ();
protected:
NSView* parent;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSOpenGLView* platformView;
#pragma clang diagnostic pop
IOpenGLView* view;
PixelFormat pixelFormat;
};
} // VSTGUI
#endif // MAC_COCOA
#endif // VSTGUI_OPENGL_SUPPORT
@@ -0,0 +1,339 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#define GL_SILENCE_DEPRECATION
#import "cocoaopenglview.h"
#if MAC_COCOA
#if VSTGUI_OPENGL_SUPPORT
#import "nsviewframe.h"
#import "cocoahelpers.h"
#import "objcclassbuilder.h"
#import "autoreleasepool.h"
#import <OpenGL/OpenGL.h>
#import <vector>
//-----------------------------------------------------------------------------
@interface NSObject (VSTGUI_NSOpenGLView)
- (id)initWithFrame:(NSRect)frameRect
pixelFormat:(NSOpenGLPixelFormat*)format
callback:(VSTGUI::CocoaOpenGLView*)callback;
@end
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
struct VSTGUI_NSOpenGLView : RuntimeObjCClass<VSTGUI_NSOpenGLView>
{
static constexpr const auto cocoaOpenGLViewVarName = "cocoaOpenGLView";
//-----------------------------------------------------------------------------
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("VSTGUI_NSOpenGLView", [NSOpenGLView class])
.addMethod (@selector (initWithFrame:pixelFormat:callback:), Init)
.addMethod (@selector (dealloc), Dealloc)
.addMethod (@selector (update), Update_Reshape)
.addMethod (@selector (reshape), Update_Reshape)
.addMethod (@selector (isFlipped), IsFlipped)
.addMethod (@selector (drawRect:), DrawRect)
.addMethod (@selector (mouseMoved:), MouseXXX)
.addMethod (@selector (rightMouseDown:), MouseXXX)
.addMethod (@selector (rightMouseUp:), MouseXXX)
.addIvar<CocoaOpenGLView*> (cocoaOpenGLViewVarName)
.finalize ();
}
//-----------------------------------------------------------------------------
static id Init (id self, SEL _cmd, NSRect frameRect, NSOpenGLPixelFormat* format,
CocoaOpenGLView* callback)
{
auto obj = makeInstance (self);
self = obj.callSuper<id (id, SEL, NSRect, NSOpenGLPixelFormat*), id> (
@selector (initWithFrame:pixelFormat:), frameRect, format);
if (self)
{
if (auto var = obj.getVariable<CocoaOpenGLView*> (cocoaOpenGLViewVarName))
{
var->set (callback);
callback->remember ();
}
}
return self;
}
//-----------------------------------------------------------------------------
static void Dealloc (id self, SEL _cmd)
{
auto obj = makeInstance (self);
if (auto var = obj.getVariable<CocoaOpenGLView*> (cocoaOpenGLViewVarName))
{
if (auto callback = var->get ())
callback->forget ();
}
obj.callSuper<void (id, SEL)> (_cmd);
}
//-----------------------------------------------------------------------------
static void Update_Reshape (id self, SEL _cmd)
{
auto obj = makeInstance (self);
if (auto var = obj.getVariable<CocoaOpenGLView*> (cocoaOpenGLViewVarName))
{
if (auto callback = var->get ())
callback->reshape ();
}
}
//------------------------------------------------------------------------------------
static BOOL IsFlipped (id self, SEL _cmd) { return YES; }
//------------------------------------------------------------------------------------
static void DrawRect (id self, SEL _cmd, NSRect rect)
{
auto obj = makeInstance (self);
if (auto var = obj.getVariable<CocoaOpenGLView*> (cocoaOpenGLViewVarName))
{
if (auto callback = var->get ())
callback->doDraw (rectFromNSRect (rect));
}
}
//------------------------------------------------------------------------------------
static void MouseXXX (id self, SEL _cmd, NSEvent* theEvent)
{
if ([self nextResponder])
[[self nextResponder] performSelector:_cmd withObject:theEvent];
}
};
#ifdef MAC_OS_X_VERSION_10_12
static constexpr auto CocoaOpenGLContextParameterSwapInterval = NSOpenGLContextParameterSwapInterval;
static constexpr auto CocoaOpenGLContextParameterSurfaceOpacity = NSOpenGLContextParameterSurfaceOpacity;
#else
static constexpr auto CocoaOpenGLContextParameterSwapInterval = NSOpenGLCPSwapInterval;
static constexpr auto CocoaOpenGLContextParameterSurfaceOpacity = NSOpenGLCPSurfaceOpacity;
#endif
//-----------------------------------------------------------------------------
CocoaOpenGLView::CocoaOpenGLView (NSView* parent)
: parent (parent)
, platformView (nullptr)
, view (nullptr)
{
}
//-----------------------------------------------------------------------------
bool CocoaOpenGLView::init (IOpenGLView* inView, PixelFormat* _pixelFormat)
{
if (platformView)
return false;
if (parent)
{
NSRect r = NSMakeRect (0, 0, 100, 100);
std::vector<NSOpenGLPixelFormatAttribute> formatAttributes;
if (_pixelFormat)
{
pixelFormat = *_pixelFormat;
formatAttributes.emplace_back (NSOpenGLPFADepthSize);
formatAttributes.emplace_back (pixelFormat.depthBufferSize);
formatAttributes.emplace_back (NSOpenGLPFAStencilSize);
formatAttributes.emplace_back (pixelFormat.stencilBufferSize);
formatAttributes.emplace_back (NSOpenGLPFANoRecovery);
formatAttributes.emplace_back (NSOpenGLPFAAccelerated);
if (pixelFormat.flags & PixelFormat::kDoubleBuffered)
{
formatAttributes.emplace_back (NSOpenGLPFADoubleBuffer);
formatAttributes.emplace_back (NSOpenGLPFABackingStore);
}
if (pixelFormat.flags & PixelFormat::kMultiSample)
{
formatAttributes.emplace_back (NSOpenGLPFAMultisample);
formatAttributes.emplace_back (true);
formatAttributes.emplace_back (NSOpenGLPFASampleBuffers);
formatAttributes.emplace_back (2);
formatAttributes.emplace_back (NSOpenGLPFASamples);
formatAttributes.emplace_back (pixelFormat.samples);
}
if (pixelFormat.flags & PixelFormat::kModernOpenGL)
{
formatAttributes.emplace_back (NSOpenGLPFAOpenGLProfile);
formatAttributes.emplace_back (NSOpenGLProfileVersion3_2Core);
}
else
{
formatAttributes.emplace_back (NSOpenGLPFAOpenGLProfile);
formatAttributes.emplace_back (NSOpenGLProfileVersionLegacy);
}
}
else
{
formatAttributes.emplace_back (NSOpenGLPFANoRecovery);
formatAttributes.emplace_back (NSOpenGLPFAAccelerated);
formatAttributes.emplace_back (NSOpenGLPFADoubleBuffer);
formatAttributes.emplace_back (NSOpenGLPFABackingStore);
formatAttributes.emplace_back (NSOpenGLPFADepthSize);
formatAttributes.emplace_back (32);
formatAttributes.emplace_back (NSOpenGLPFAOpenGLProfile);
formatAttributes.emplace_back (NSOpenGLProfileVersionLegacy);
}
formatAttributes.emplace_back (0);
NSOpenGLPixelFormat* nsPixelFormat = [[[NSOpenGLPixelFormat alloc]
initWithAttributes:&formatAttributes.front ()] autorelease];
platformView = [VSTGUI_NSOpenGLView::alloc () initWithFrame:r
pixelFormat:nsPixelFormat
callback:this];
if (platformView)
{
NSOpenGLContext* context = [platformView openGLContext];
GLint value = 1;
[context setValues:&value forParameter:CocoaOpenGLContextParameterSwapInterval];
value = 0;
[context setValues:&value forParameter:CocoaOpenGLContextParameterSurfaceOpacity];
#if DEBUG
if (pixelFormat.flags & PixelFormat::kModernOpenGL)
{
CGLEnable (static_cast<CGLContextObj> ([context CGLContextObj]),
kCGLCECrashOnRemovedFunctions);
}
#endif
view = inView;
platformView.wantsBestResolutionOpenGLSurface = YES;
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
void CocoaOpenGLView::remove ()
{
if (platformView)
{
AutoreleasePool ap;
[platformView removeFromSuperview];
[platformView release];
platformView = nil;
view = nullptr;
}
}
//-----------------------------------------------------------------------------
void CocoaOpenGLView::invalidRect (const CRect& rect)
{
if (platformView)
{
[platformView setNeedsDisplayInRect:nsRectFromCRect (rect)];
}
}
//-----------------------------------------------------------------------------
bool CocoaOpenGLView::makeContextCurrent ()
{
if (platformView)
{
NSOpenGLContext* context = [platformView openGLContext];
if (context)
{
[context makeCurrentContext];
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
bool CocoaOpenGLView::lockContext ()
{
if (platformView)
{
NSOpenGLContext* context = [platformView openGLContext];
if (context)
{
CGLLockContext ((CGLContextObj)[context CGLContextObj]);
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
bool CocoaOpenGLView::unlockContext ()
{
if (platformView)
{
NSOpenGLContext* context = [platformView openGLContext];
if (context)
{
CGLUnlockContext ((CGLContextObj)[context CGLContextObj]);
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
void CocoaOpenGLView::swapBuffers ()
{
if (platformView && pixelFormat.flags & PixelFormat::kDoubleBuffered)
{
NSOpenGLContext* context = [platformView openGLContext];
if (context)
{
[context flushBuffer];
[NSOpenGLContext clearCurrentContext];
}
}
}
//-----------------------------------------------------------------------------
void CocoaOpenGLView::viewSizeChanged (const CRect& visibleSize)
{
if (platformView)
{
lockContext ();
NSRect r = nsRectFromCRect (visibleSize);
[platformView setFrame:r];
if ([platformView superview] == nil)
{
[parent addSubview:platformView];
}
unlockContext ();
}
}
//-----------------------------------------------------------------------------
void CocoaOpenGLView::doDraw (const CRect& rect)
{
if (view)
{
lockContext ();
view->drawOpenGL (rect);
unlockContext ();
}
}
//-----------------------------------------------------------------------------
void CocoaOpenGLView::reshape ()
{
lockContext ();
NSOpenGLContext* context = [platformView openGLContext];
if (context)
[context update];
unlockContext ();
view->reshape ();
[platformView setNeedsDisplay:YES];
}
} // VSTGUI
#endif // VSTGUI_OPENGL_SUPPORT
#endif // MAC_COCOA
@@ -0,0 +1,45 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../iplatformtextedit.h"
#if MAC_COCOA
#ifdef __OBJC__
@class NSView;
@class NSTextField;
#else
struct NSView;
struct NSTextField;
#endif
namespace VSTGUI {
//-----------------------------------------------------------------------------
class CocoaTextEdit : public IPlatformTextEdit
{
public:
CocoaTextEdit (NSView* parent, IPlatformTextEditCallback* textEdit);
~CocoaTextEdit () noexcept override;
UTF8String getText () override;
bool setText (const UTF8String& text) override;
bool updateSize () override;
bool drawsPlaceholder () const override { return true; }
NSTextField* getPlatformControl () const { return platformControl; }
NSView* getParent () const { return parent; }
IPlatformTextEditCallback* getTextEdit () const { return textEdit; }
//-----------------------------------------------------------------------------
protected:
NSTextField* platformControl;
NSView* parent;
};
} // VSTGUI
#endif // MAC_COCOA
@@ -0,0 +1,345 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "cocoatextedit.h"
#if MAC_COCOA
#import "cocoahelpers.h"
#import "objcclassbuilder.h"
#import "autoreleasepool.h"
#import "../cfontmac.h"
#import "../macstring.h"
#import "../../../events.h"
//------------------------------------------------------------------------------------
@interface NSObject (VSTGUI_NSTextField_Private)
-(id)initWithTextEdit:(id)textEit;
@end
//------------------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------------------
namespace MacTextAlignment {
#ifdef MAC_OS_X_VERSION_10_12
static constexpr auto Right = ::NSTextAlignmentRight;
static constexpr auto Center = ::NSTextAlignmentCenter;
#else
static constexpr auto Right = ::NSRightTextAlignment;
static constexpr auto Center = ::NSCenterTextAlignment;
#endif
//------------------------------------------------------------------------------------
} // MacTextAlignment
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
template<bool SecureT>
struct VSTGUI_NSTextFieldT : RuntimeObjCClass<VSTGUI_NSTextFieldT<SecureT>>
{
using Base = RuntimeObjCClass<VSTGUI_NSTextFieldT<SecureT>>;
static constexpr const auto textEditVarName = "_textEdit";
static Class CreateClass ()
{
if constexpr (SecureT)
{
return ObjCClassBuilder ()
.init ("VSTGUI_NSSecureTextField", [NSSecureTextField class])
.addMethod (@selector (initWithTextEdit:), Init)
.addMethod (@selector (syncSize), SyncSize)
.addMethod (@selector (removeFromSuperview), RemoveFromSuperview)
.addMethod (@selector (control:textView:doCommandBySelector:), DoCommandBySelector)
.addMethod (@selector (textDidChange:), TextDidChange)
.addIvar<IPlatformTextEditCallback*> (textEditVarName)
.finalize ();
}
else
{
return ObjCClassBuilder ()
.init ("VSTGUI_NSTextField", [NSTextField class])
.addMethod (@selector (initWithTextEdit:), Init)
.addMethod (@selector (syncSize), SyncSize)
.addMethod (@selector (removeFromSuperview), RemoveFromSuperview)
.addMethod (@selector (control:textView:doCommandBySelector:), DoCommandBySelector)
.addMethod (@selector (textDidChange:), TextDidChange)
.addIvar<CocoaTextEdit*> (textEditVarName)
.finalize ();
}
}
static id Init (id self, SEL _cmd, void* textEdit)
{
if (self)
{
CocoaTextEdit* te = (CocoaTextEdit*)textEdit;
IPlatformTextEditCallback* tec = te->getTextEdit ();
NSView* frameView = te->getParent ();
NSRect editFrameRect = nsRectFromCRect (tec->platformGetSize ());
NSView* containerView = [[NSView alloc] initWithFrame:editFrameRect];
[containerView setAutoresizesSubviews:YES];
if ([frameView wantsLayer])
{
containerView.wantsLayer = YES;
double maxZPosition = -1.;
for (CALayer* layer in frameView.layer.sublayers)
{
double zPosition = layer.zPosition;
if (zPosition > maxZPosition)
maxZPosition = zPosition;
}
[containerView layer].zPosition = static_cast<CGFloat> (maxZPosition + 1);
}
CPoint textInset = tec->platformGetTextInset ();
editFrameRect.origin.x = static_cast<CGFloat> (textInset.x / 2. - 1.);
editFrameRect.origin.y = static_cast<CGFloat> (textInset.y / 2.);
editFrameRect.size.width -= textInset.x / 2.;
editFrameRect.size.height -= textInset.y / 2. - 1.;
self = Base::makeInstance (self).template callSuper<id (id, SEL, NSRect), id> (
@selector (initWithFrame:), editFrameRect);
if (!self)
{
[containerView release];
return nil;
}
auto obj = Base::makeInstance (self);
if (auto var = obj.template getVariable<IPlatformTextEditCallback*> (textEditVarName))
var->set (tec);
CoreTextFont* ctf = tec->platformGetFont ()->getPlatformFont ().cast<CoreTextFont> ();
if (ctf)
{
CTFontRef fontRef = ctf->getFontRef ();
if (fontRef)
{
CTFontDescriptorRef fontDesc = CTFontCopyFontDescriptor (fontRef);
[self setFont:[NSFont fontWithDescriptor:(NSFontDescriptor*)fontDesc
size:ctf->getSize ()]];
CFRelease (fontDesc);
}
}
NSString* text = fromUTF8String<NSString*> (tec->platformGetText ());
NSString* placeholder = fromUTF8String<NSString*> (tec->platformGetPlaceholderText ());
[self setTextColor:nsColorFromCColor (tec->platformGetFontColor ())];
[self setBordered:NO];
[self setAllowsEditingTextAttributes:NO];
[self setImportsGraphics:NO];
[self setStringValue:text];
[self setFocusRingType:NSFocusRingTypeNone];
[self sizeToFit];
[(NSView*)self setAutoresizingMask:NSViewMinYMargin];
if ([self frame].size.height < editFrameRect.size.height)
{
CGFloat offset = editFrameRect.size.height - [self frame].size.height;
editFrameRect.origin.y = static_cast<CGFloat> (offset / 2.);
editFrameRect.size.height = [self frame].size.height;
}
else
editFrameRect.size.height = [self frame].size.height;
[self setFrame:editFrameRect];
[containerView addSubview:self];
[self performSelector:@selector (syncSize)];
[frameView addSubview:containerView];
NSTextFieldCell* cell = [self cell];
[cell setDrawsBackground:NO];
[cell setLineBreakMode:NSLineBreakByClipping];
[cell setScrollable:YES];
if (tec->platformGetHoriTxtAlign () == kCenterText)
[cell setAlignment:MacTextAlignment::Center];
else if (tec->platformGetHoriTxtAlign () == kRightText)
[cell setAlignment:MacTextAlignment::Right];
if (placeholder.length > 0)
{
CColor color = tec->platformGetFontColor ();
color.alpha /= 2;
NSMutableParagraphStyle* paragraphStyle =
[[[NSMutableParagraphStyle alloc] init] autorelease];
if (tec->platformGetHoriTxtAlign () == kCenterText)
paragraphStyle.alignment = MacTextAlignment::Center;
else if (tec->platformGetHoriTxtAlign () == kRightText)
paragraphStyle.alignment = MacTextAlignment::Right;
NSDictionary* attrDict = [NSDictionary
dictionaryWithObjectsAndKeys:[self font], NSFontAttributeName,
nsColorFromCColor (color),
NSForegroundColorAttributeName, paragraphStyle,
NSParagraphStyleAttributeName, nil];
NSAttributedString* as =
[[[NSAttributedString alloc] initWithString:placeholder
attributes:attrDict] autorelease];
[cell setPlaceholderAttributedString:as];
}
[self setDelegate:self];
[self setNextKeyView:frameView];
if (auto tv = static_cast<NSTextView*> ([[self window] fieldEditor:YES forObject:self]))
tv.insertionPointColor = nsColorFromCColor (tec->platformGetFontColor ());
dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t) (1 * NSEC_PER_MSEC)),
dispatch_get_main_queue (), ^{
[[self window] makeFirstResponder:self];
});
}
return self;
}
//------------------------------------------------------------------------------------
static void SyncSize (id self, SEL _cmd)
{
if (auto te = Base::makeInstance (self).template getVariable<IPlatformTextEditCallback*> (
textEditVarName))
{
auto textEdit = te->get ();
if (!textEdit)
return;
NSView* containerView = [self superview];
CRect rect (textEdit->platformGetVisibleSize ());
rect.makeIntegral ();
[containerView setFrame:nsRectFromCRect (rect)];
rect.extend (15, -15);
[[containerView superview] setNeedsDisplayInRect:nsRectFromCRect (rect)];
}
}
//------------------------------------------------------------------------------------
static void RemoveFromSuperview (id self, SEL _cmd)
{
auto obj = Base::makeInstance (self);
if (auto var = obj.template getVariable<IPlatformTextEditCallback*> (textEditVarName))
var->set (nullptr);
NSView* containerView = [self superview];
if (containerView)
{
[[containerView window] makeFirstResponder:[containerView superview]];
[containerView removeFromSuperview];
// [super removeFromSuperview];
obj.template callSuper<void (id, SEL)> (_cmd);
[containerView release];
}
}
//------------------------------------------------------------------------------------
static void TextDidChange (id self, SEL _cmd, NSNotification* notification)
{
auto obj = Base::makeInstance (self);
if (auto var = obj.template getVariable<IPlatformTextEditCallback*> (textEditVarName))
{
if (auto te = var->get ())
te->platformTextDidChange ();
}
obj.template callSuper<void (id, SEL, NSNotification*)> (_cmd, notification);
}
//------------------------------------------------------------------------------------
static BOOL DoCommandBySelector (id self, SEL _cmd, NSControl* control, NSTextView* textView,
SEL commandSelector)
{
auto var = Base::makeInstance (self).template getVariable<IPlatformTextEditCallback*> (
textEditVarName);
if (!var)
return NO;
IPlatformTextEditCallback* tec = var->get ();
if (!tec)
return NO;
if (commandSelector == @selector (insertNewline:))
{
KeyboardEvent event;
event.type = EventType::KeyDown;
event.virt = VirtualKey::Return;
tec->platformOnKeyboardEvent (event);
if (event.consumed)
return YES;
}
else if (commandSelector == @selector (insertTab:))
{
KeyboardEvent event;
event.type = EventType::KeyDown;
event.virt = VirtualKey::Tab;
tec->platformOnKeyboardEvent (event);
if (event.consumed)
return YES;
}
else if (commandSelector == @selector (insertBacktab:))
{
KeyboardEvent event;
event.type = EventType::KeyDown;
event.virt = VirtualKey::Tab;
event.modifiers.add (ModifierKey::Shift);
tec->platformOnKeyboardEvent (event);
if (event.consumed)
return YES;
}
else if (commandSelector == @selector (cancelOperation:))
{
KeyboardEvent event;
event.type = EventType::KeyDown;
event.virt = VirtualKey::Escape;
tec->platformOnKeyboardEvent (event);
if (event.consumed)
return YES;
}
return NO;
}
};
using VSTGUI_NSTextField = VSTGUI_NSTextFieldT<false>;
using VSTGUI_NSTextField_Secure = VSTGUI_NSTextFieldT<true>;
//-----------------------------------------------------------------------------
CocoaTextEdit::CocoaTextEdit (NSView* parent, IPlatformTextEditCallback* textEdit)
: IPlatformTextEdit (textEdit)
, platformControl (nullptr)
, parent (parent)
{
if (textEdit->platformIsSecureTextEdit ())
platformControl = [VSTGUI_NSTextField_Secure::alloc () initWithTextEdit:(id)this];
else
platformControl = [VSTGUI_NSTextField::alloc () initWithTextEdit:(id)this];
}
//-----------------------------------------------------------------------------
CocoaTextEdit::~CocoaTextEdit () noexcept
{
[platformControl performSelector:@selector(removeFromSuperview)];
[platformControl performSelector:@selector(autorelease)];
}
//-----------------------------------------------------------------------------
UTF8String CocoaTextEdit::getText ()
{
return [[[platformControl stringValue] decomposedStringWithCanonicalMapping] UTF8String];
}
//-----------------------------------------------------------------------------
bool CocoaTextEdit::updateSize ()
{
[platformControl performSelector:@selector(syncSize)];
return true;
}
//-----------------------------------------------------------------------------
bool CocoaTextEdit::setText (const UTF8String& text)
{
[platformControl setStringValue:fromUTF8String<NSString*> (text)];
return true;
}
//-----------------------------------------------------------------------------
} // VSTGUI
#endif // MAC_COCOA
@@ -0,0 +1,50 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../vstguifwd.h"
#if MAC_COCOA && !TARGET_OS_IPHONE
#include "../../../dragging.h"
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
struct NSView;
struct NSDraggingSession;
struct NSImage;
#endif
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
struct NSViewDraggingSession : public IDraggingSession, public NonAtomicReferenceCounted
{
static SharedPointer<NSViewDraggingSession> create (
NSView* view, const DragDescription& desc, const SharedPointer<IDragCallback>& callback);
NSViewDraggingSession (NSDraggingSession* session, const DragDescription& desc,
const SharedPointer<IDragCallback>& callback);
bool setBitmap (const SharedPointer<CBitmap>& bitmap, CPoint offset) override;
void dragWillBegin (CPoint pos);
void dragMoved (CPoint pos);
void dragEnded (CPoint pos, DragOperation result);
private:
static NSImage* nsImageForDragOperation (CBitmap* bitmap);
NSDraggingSession* session;
DragDescription desc;
SharedPointer<IDragCallback> callback;
};
//------------------------------------------------------------------------
} // VSTGUI
#endif
@@ -0,0 +1,261 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "nsviewdraggingsession.h"
#if MAC_COCOA
#import "../cgbitmap.h"
#import "../macclipboard.h"
#import "cocoahelpers.h"
#import "objcclassbuilder.h"
//------------------------------------------------------------------------
@interface NSObject (VSTGUI_BinaryDataType_Private)
- (id)initWithData:(const void*)data andSize:(size_t)size;
@end
#ifndef MAC_OS_X_VERSION_10_13
#define MAC_OS_X_VERSION_10_13 101300
#endif
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
typedef NSString *NSPasteboardType;
typedef NSString *NSPasteboardReadingOptionKey;
#endif
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
struct BinaryDataType : RuntimeObjCClass<BinaryDataType>
{
static constexpr const auto dataVarName = "_data";
static NSString* getCocoaPasteboardTypeString ()
{
return [NSString stringWithCString:VSTGUI::MacClipboard::getPasteboardBinaryType ()
encoding:NSASCIIStringEncoding];
}
static id Init (id self, SEL, const void* buffer, size_t bufferSize)
{
auto obj = makeInstance (self);
self = obj.callSuper<id (id, SEL), id> (@selector (init));
if (self)
{
auto data = [[NSData alloc] initWithBytes:buffer length:bufferSize];
if (auto var = obj.getVariable<NSData*> (dataVarName))
var->set (data);
}
return self;
}
static void Dealloc (id self, SEL _cmd)
{
auto obj = makeInstance (self);
if (auto var = obj.getVariable<NSData*> (dataVarName); var.has_value () && var->get ())
{
[var->get () release];
var->set (nullptr);
}
obj.callSuper<void (id, SEL)> (_cmd);
}
static NSArray<NSPasteboardType>* WritableTypesForPasteboard (id, SEL, NSPasteboard*)
{
return @[getCocoaPasteboardTypeString ()];
}
static id PasteboardPropertyListForType (id self, SEL, NSPasteboardType)
{
auto obj = makeInstance (self);
if (auto var = obj.getVariable<NSData*> (dataVarName); var.has_value () && var->get ())
return var->get ();
return nullptr;
}
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("VSTGUI_BinaryDataType", [NSObject class])
.addProtocol ("NSPasteboardWriting")
.addMethod (@selector (initWithData:andSize:), Init)
.addMethod (@selector (dealloc), Dealloc)
.addMethod (@selector (writableTypesForPasteboard:), WritableTypesForPasteboard)
.addMethod (@selector (pasteboardPropertyListForType:), PasteboardPropertyListForType)
.addIvar<NSData*> (dataVarName)
.finalize ();
}
};
//------------------------------------------------------------------------
SharedPointer<NSViewDraggingSession> NSViewDraggingSession::create (
NSView* nsView, const DragDescription& desc, const SharedPointer<IDragCallback>& callback)
{
NSEvent* event = [NSApp currentEvent];
if (event == nullptr || !([event type] == MacEventType::LeftMouseDown ||
[event type] == MacEventType::LeftMouseDragged))
return nullptr;
NSPoint bitmapOffset = {static_cast<CGFloat> (desc.bitmapOffset.x),
static_cast<CGFloat> (desc.bitmapOffset.y)};
auto bitmap = desc.bitmap;
NSPoint nsLocation = [event locationInWindow];
NSImage* nsImage = nil;
if ((nsImage = nsImageForDragOperation (bitmap)))
{
nsLocation = [nsView convertPoint:nsLocation fromView:nil];
bitmapOffset.x += nsLocation.x;
bitmapOffset.y += nsLocation.y + [nsImage size].height;
}
NSMutableArray* dragItems = [[NSMutableArray new] autorelease];
for (uint32_t index = 0, count = desc.data->getCount (); index < count; ++index)
{
const void* buffer = nullptr;
IDataPackage::Type type {};
auto size = desc.data->getData (index, buffer, type);
if (size == 0)
continue;
NSDraggingItem* item = nil;
switch (type)
{
case IDataPackage::kFilePath:
{
if (auto fileUrl = [NSURL
fileURLWithPath:[NSString
stringWithUTF8String:reinterpret_cast<const char*> (
buffer)]])
{
item = [[[NSDraggingItem alloc] initWithPasteboardWriter:fileUrl] autorelease];
}
break;
}
case IDataPackage::kText:
{
if (auto string =
[NSString stringWithUTF8String:reinterpret_cast<const char*> (buffer)])
{
item = [[[NSDraggingItem alloc] initWithPasteboardWriter:string] autorelease];
}
break;
}
case IDataPackage::kBinary:
{
if (id data = [[BinaryDataType::alloc () initWithData:buffer
andSize:size] autorelease])
item = [[[NSDraggingItem alloc] initWithPasteboardWriter:data] autorelease];
break;
}
case IDataPackage::kError: { continue;
}
}
if (item)
{
if (nsImage && [dragItems count] == 0)
{
NSRect r;
r.size.width = bitmap->getWidth ();
r.size.height = bitmap->getHeight ();
r.origin = bitmapOffset;
r.origin.y -= r.size.height;
[item setDraggingFrame:r contents:nsImage];
}
else
{
NSRect r;
r.origin = bitmapOffset;
r.size = NSMakeSize (1, 1);
item.draggingFrame = r;
}
[dragItems addObject:item];
}
}
NSView<NSDraggingSource>* draggingSource = (NSView<NSDraggingSource>*)nsView;
if (auto session =
[nsView beginDraggingSessionWithItems:dragItems event:event source:draggingSource])
{
session.animatesToStartingPositionsOnCancelOrFail = YES;
return makeOwned<NSViewDraggingSession> (session, desc, callback);
}
return nullptr;
}
//------------------------------------------------------------------------
NSViewDraggingSession::NSViewDraggingSession (NSDraggingSession* session,
const DragDescription& desc,
const SharedPointer<IDragCallback>& callback)
: session (session), desc (desc), callback (callback)
{
}
//------------------------------------------------------------------------
bool NSViewDraggingSession::setBitmap (const SharedPointer<CBitmap>& bitmap, CPoint offset)
{
[session enumerateDraggingItemsWithOptions:0
forView:nil
classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
searchOptions:[NSDictionary<NSPasteboardReadingOptionKey, id> new]
usingBlock:[&] (NSDraggingItem* _Nonnull draggingItem,
NSInteger idx, BOOL* _Nonnull stop) {
if (idx != 0)
return;
if (auto nsImage = nsImageForDragOperation (bitmap))
{
NSRect r;
r.origin = nsPointFromCPoint (offset);
r.origin.y -= nsImage.size.height;
r.size = nsImage.size;
[draggingItem setDraggingFrame:r contents:nsImage];
}
else
{
[draggingItem
setDraggingFrame:draggingItem.draggingFrame
contents:nil];
}
*stop = YES;
}];
desc.bitmap = bitmap;
desc.bitmapOffset = offset;
return true;
}
//------------------------------------------------------------------------
void NSViewDraggingSession::dragWillBegin (CPoint pos)
{
if (!callback)
return;
callback->dragWillBegin (this, pos);
}
//------------------------------------------------------------------------
void NSViewDraggingSession::dragMoved (CPoint pos)
{
if (!callback)
return;
callback->dragMoved (this, pos);
}
//------------------------------------------------------------------------
void NSViewDraggingSession::dragEnded (CPoint pos, DragOperation result)
{
if (!callback)
return;
callback->dragEnded (this, pos, result);
}
//-----------------------------------------------------------------------------
NSImage* NSViewDraggingSession::nsImageForDragOperation (CBitmap* bitmap)
{
return [bitmapToNSImage (bitmap) autorelease];
}
//------------------------------------------------------------------------
} // VSTGUI
#endif
@@ -0,0 +1,128 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../vstguifwd.h"
#if MAC_COCOA && !TARGET_OS_IPHONE
#include "../../platform_macos.h"
#include "../../iplatformtextinputclient.h"
#include "../../../cinvalidrectlist.h"
#include "../../../idatapackage.h"
#import "../coregraphicsdevicecontext.h"
#include "nsviewdraggingsession.h"
#include <list>
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#else
struct NSView;
struct NSRect;
struct NSDraggingSession;
struct NSEvent;
struct CALayer;
#endif
namespace VSTGUI {
class CocoaTooltipWindow;
struct NSViewDraggingSession;
//-----------------------------------------------------------------------------
class NSViewFrame : public IPlatformFrame, public ICocoaPlatformFrame, public IPlatformFrameTouchBarExtension
{
public:
NSViewFrame (IPlatformFrameCallback* frame, const CRect& size, NSView* parent, IPlatformFrameConfig* config);
~NSViewFrame () noexcept override;
NSView* getNSView () const override { return nsView; }
void setTextInputClient (ICocoaTextInputClient* client) override;
ICocoaTextInputClient* getTextInputClient () const { return textInputClient; }
CALayer* getCALayer () const { return caLayer; }
IPlatformFrameCallback* getFrame () const { return frame; }
void* makeTouchBar () const;
NSViewDraggingSession* getDraggingSession () const { return draggingSession; }
void clearDraggingSession () { draggingSession = nullptr; }
void setNeedsDisplayInRect (NSRect r);
#if VSTGUI_ENABLE_DEPRECATED_METHODS
void setLastDragOperationResult (DragResult result) { lastDragOperationResult = result; }
#endif
void setDragDataPackage (SharedPointer<IDataPackage>&& package) { dragDataPackage = std::move (package); }
const SharedPointer<IDataPackage>& getDragDataPackage () const { return dragDataPackage; }
void initTrackingArea ();
void scaleFactorChanged (double newScaleFactor);
void cursorUpdate ();
void drawLayer (CALayer* layer, CGContextRef ctx);
void drawRect (NSRect* rect);
bool onMouseDown (NSEvent* evt);
bool onMouseUp (NSEvent* evt);
bool onMouseMoved (NSEvent* evt);
// IPlatformFrame
bool getGlobalPosition (CPoint& pos) const override;
bool setSize (const CRect& newSize) override;
bool getSize (CRect& size) const override;
bool getCurrentMousePosition (CPoint& mousePosition) const override;
bool getCurrentMouseButtons (CButtonState& buttons) const override;
bool getCurrentModifiers (Modifiers& modifiers) const override;
bool setMouseCursor (CCursorType type) override;
bool invalidRect (const CRect& rect) override;
bool scrollRect (const CRect& src, const CPoint& distance) override;
bool showTooltip (const CRect& rect, const char* utf8Text) override;
bool hideTooltip () override;
void* getPlatformRepresentation () const override { return nsView; }
SharedPointer<IPlatformTextEdit> createPlatformTextEdit (IPlatformTextEditCallback* textEdit) override;
SharedPointer<IPlatformOptionMenu> createPlatformOptionMenu () override;
#if VSTGUI_OPENGL_SUPPORT
SharedPointer<IPlatformOpenGLView> createPlatformOpenGLView () override;
#endif
SharedPointer<IPlatformViewLayer> createPlatformViewLayer (IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer = nullptr) override;
#if VSTGUI_ENABLE_DEPRECATED_METHODS
DragResult doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) override;
#endif
bool doDrag (const DragDescription& dragDescription, const SharedPointer<IDragCallback>& callback) override;
PlatformType getPlatformType () const override { return PlatformType::kNSView; }
void onFrameClosed () override {}
Optional<UTF8String> convertCurrentKeyEventToText () override;
bool setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme = nullptr) override;
// IPlatformFrameTouchBarExtension
void setTouchBarCreator (const SharedPointer<ITouchBarCreator>& creator) override;
void recreateTouchBar () override;
//-----------------------------------------------------------------------------
protected:
void draw (CGContextRef context, CRect updateRect, double scaleFactor);
void addDebugRedrawRect (CRect r, bool isClipBoundingBox = false);
NSView* nsView {nullptr};
CALayer* caLayer {nullptr};
CocoaTooltipWindow* tooltipWindow {nullptr};
ICocoaTextInputClient* textInputClient {nullptr};
SharedPointer<IDataPackage> dragDataPackage;
SharedPointer<ITouchBarCreator> touchBarCreator;
SharedPointer<NSViewDraggingSession> draggingSession;
std::unique_ptr<GenericOptionMenuTheme> genericOptionMenuTheme;
#if VSTGUI_ENABLE_DEPRECATED_METHODS
DragResult lastDragOperationResult;
#endif
bool trackingAreaInitialized;
bool inDraw;
bool useInvalidRects {false};
CCursorType cursor;
CButtonState mouseDownButtonState {};
CInvalidRectList invalidRectList;
};
} // VSTGUI
#endif // MAC_COCOA
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../iplatformoptionmenu.h"
#if MAC_COCOA
namespace VSTGUI {
//-----------------------------------------------------------------------------
class NSViewOptionMenu : public IPlatformOptionMenu
{
public:
void popup (COptionMenu* optionMenu, const Callback& callback) override;
};
} // VSTGUI
#endif // MAC_COCOA
@@ -0,0 +1,329 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "nsviewoptionmenu.h"
#if MAC_COCOA
#import "../../../cbitmap.h"
#import "../../../cframe.h"
#import "../../../controls/coptionmenu.h"
#import "../cgbitmap.h"
#import "../macstring.h"
#import "cocoahelpers.h"
#import "nsviewframe.h"
#import "objcclassbuilder.h"
@interface NSObject (VSTGUI_NSMenu_Private)
- (id)initWithOptionMenu:(id)menu;
@end
namespace VSTGUI {
#ifndef MAC_OS_X_VERSION_10_14
#define MAC_OS_X_VERSION_10_14 101400
#endif
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_14
static constexpr auto NSControlStateValueOn = NSOnState;
static constexpr auto NSControlStateValueOff = NSOffState;
#endif
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
struct VSTGUI_NSMenu : RuntimeObjCClass<VSTGUI_NSMenu>
{
static constexpr const auto privateVarName = "_private";
int32_t menuClassCount = 0;
//------------------------------------------------------------------------------------
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("VSTGUI_NSMenu", [NSMenu class])
.addMethod (@selector (initWithOptionMenu:), Init)
.addMethod (@selector (dealloc), Dealloc)
.addMethod (@selector (validateMenuItem:), ValidateMenuItem)
.addMethod (@selector (menuItemSelected:), MenuItemSelected)
.addMethod (@selector (optionMenu), OptionMenu)
.addMethod (@selector (selectedMenu), SelectedMenu)
.addMethod (@selector (selectedItem), SelectedItem)
.addMethod (@selector (setSelectedMenu:), SetSelectedMenu)
.addMethod (@selector (setSelectedItem:), SetSelectedItem)
.addIvar<VSTGUI_NSMenu::Var*> (privateVarName)
.finalize ();
}
//------------------------------------------------------------------------------------
~VSTGUI_NSMenu () noexcept { vstgui_assert (menuClassCount == 0); }
//------------------------------------------------------------------------------------
struct Var
{
COptionMenu* _optionMenu {nullptr};
COptionMenu* _selectedMenu {nullptr};
int32_t _selectedItem {0};
};
//------------------------------------------------------------------------------------
static id Init (id self, SEL _cmd, void* _menu)
{
instance ().menuClassCount++;
auto obj = makeInstance (self);
self = obj.callSuper<id (id, SEL), id> (@selector (init));
if (self)
{
NSMenu* nsMenu = (NSMenu*)self;
COptionMenu* menu = (COptionMenu*)_menu;
Var* var = new Var;
var->_optionMenu = menu;
setVar (self, var);
int32_t index = -1;
bool multipleCheck = menu->isMultipleCheckStyle ();
CConstMenuItemIterator it = menu->getItems ()->begin ();
while (it != menu->getItems ()->end ())
{
CMenuItem* item = (*it);
it++;
index++;
NSMenuItem* nsItem = nullptr;
NSMutableString* itemTitle = [[[NSMutableString alloc]
initWithString:fromUTF8String<NSString*> (item->getTitle ())] autorelease];
if (menu->getPrefixNumbers ())
{
NSString* prefixString = nullptr;
switch (menu->getPrefixNumbers ())
{
case 2:
prefixString = [NSString stringWithFormat:@"%1d ", index + 1];
break;
case 3:
prefixString = [NSString stringWithFormat:@"%02d ", index + 1];
break;
case 4:
prefixString = [NSString stringWithFormat:@"%03d ", index + 1];
break;
}
[itemTitle insertString:prefixString atIndex:0];
}
if (item->getSubmenu ())
{
nsItem = [nsMenu addItemWithTitle:itemTitle action:nil keyEquivalent:@""];
NSMenu* subMenu = [[[[self class] alloc]
initWithOptionMenu:(id)item->getSubmenu ()] autorelease];
[nsMenu setSubmenu:subMenu forItem:nsItem];
if (multipleCheck && item->isChecked ())
[nsItem setState:NSControlStateValueOn];
else
[nsItem setState:NSControlStateValueOff];
}
else if (item->isSeparator ())
{
[nsMenu addItem:[NSMenuItem separatorItem]];
}
else
{
nsItem = [nsMenu addItemWithTitle:itemTitle
action:@selector (menuItemSelected:)
keyEquivalent:@""];
if (item->isTitle ())
[nsItem setIndentationLevel:1];
[nsItem setTarget:nsMenu];
[nsItem setTag:index];
if (multipleCheck && item->isChecked ())
[nsItem setState:NSControlStateValueOn];
else
[nsItem setState:NSControlStateValueOff];
NSString* keyEquivalent = nil;
if (!item->getKeycode ().empty ())
{
keyEquivalent = fromUTF8String<NSString*> (item->getKeycode ());
}
else if (item->getVirtualKey () != VirtualKey::None)
{
keyEquivalent = GetVirtualKeyCodeString (item->getVirtualKey ());
}
if (keyEquivalent)
{
[nsItem setKeyEquivalent:keyEquivalent];
uint32_t keyModifiers = 0;
if (item->getKeyModifiers () & kControl)
keyModifiers |= MacEventModifier::CommandKeyMask;
if (item->getKeyModifiers () & kShift)
keyModifiers |= MacEventModifier::ShiftKeyMask;
if (item->getKeyModifiers () & kAlt)
keyModifiers |= MacEventModifier::AlternateKeyMask;
if (item->getKeyModifiers () & kApple)
keyModifiers |= MacEventModifier::ControlKeyMask;
[nsItem setKeyEquivalentModifierMask:keyModifiers];
}
}
if (nsItem)
{
if (auto nsImage = bitmapToNSImage (item->getIcon ()))
{
[nsItem setImage:nsImage];
[nsImage release];
}
}
}
}
return self;
}
//-----------------------------------------------------------------------------
static void setVar (id self, Var* var)
{
if (auto v = makeInstance (self).getVariable<Var*> (privateVarName))
{
if (auto old = v->get ())
delete old;
v->set (var);
}
}
//-----------------------------------------------------------------------------
static Var* getVar (id self)
{
if (auto v = makeInstance (self).getVariable<Var*> (privateVarName); v.has_value ())
return v->get ();
return nullptr;
}
//-----------------------------------------------------------------------------
static void Dealloc (id self, SEL _cmd)
{
instance ().menuClassCount--;
setVar (self, nullptr);
makeInstance (self).callSuper<void (id, SEL)> (_cmd);
}
//------------------------------------------------------------------------------------
static BOOL ValidateMenuItem (id self, SEL _cmd, id item)
{
Var* var = getVar (self);
if (var && var->_optionMenu)
{
CMenuItem* menuItem = var->_optionMenu->getEntry ((int32_t)[item tag]);
if (!menuItem->isEnabled () || menuItem->isTitle ())
return NO;
}
return YES;
}
//------------------------------------------------------------------------------------
static void MenuItemSelected (id self, SEL _cmd, id item)
{
Var* var = getVar (self);
if (var)
{
id menu = self;
while ([menu supermenu])
menu = [menu supermenu];
[menu performSelector:@selector (setSelectedMenu:) withObject:(id)var->_optionMenu];
[menu performSelector:@selector (setSelectedItem:) withObject:(id)[item tag]];
}
}
//------------------------------------------------------------------------------------
static void* OptionMenu (id self, SEL _cmd)
{
Var* var = getVar (self);
return var ? var->_optionMenu : nullptr;
}
//------------------------------------------------------------------------------------
static void* SelectedMenu (id self, SEL _cmd)
{
Var* var = getVar (self);
return var ? var->_selectedMenu : nullptr;
}
//------------------------------------------------------------------------------------
static int32_t SelectedItem (id self, SEL _cmd)
{
Var* var = getVar (self);
return var ? var->_selectedItem : 0;
}
//------------------------------------------------------------------------------------
static void SetSelectedMenu (id self, SEL _cmd, void* menu)
{
Var* var = getVar (self);
if (var)
var->_selectedMenu = (COptionMenu*)menu;
}
//------------------------------------------------------------------------------------
static void SetSelectedItem (id self, SEL _cmd, int32_t item)
{
Var* var = getVar (self);
if (var)
var->_selectedItem = item;
}
};
//-----------------------------------------------------------------------------
void NSViewOptionMenu::popup (COptionMenu* optionMenu, const Callback& callback)
{
vstgui_assert (optionMenu && callback, "arguments are required");
PlatformOptionMenuResult result = {};
CFrame* frame = optionMenu->getFrame ();
if (!frame || !frame->getPlatformFrame ())
{
callback (optionMenu, result);
return;
}
NSViewFrame* nsViewFrame = dynamic_cast<NSViewFrame*> (frame->getPlatformFrame ());
nsViewFrame->setMouseCursor (kCursorDefault);
CRect globalSize = optionMenu->translateToGlobal (optionMenu->getViewSize ());
globalSize.offset (-frame->getViewSize ().getTopLeft ());
bool multipleCheck = optionMenu->isMultipleCheckStyle ();
NSView* view = nsViewFrame->getNSView ();
NSMenu* nsMenu = [VSTGUI_NSMenu::alloc () initWithOptionMenu:(id)optionMenu];
CPoint p = globalSize.getTopLeft ();
NSRect cellFrameRect = {};
cellFrameRect.origin = nsPointFromCPoint (p);
cellFrameRect.size.width = static_cast<CGFloat> (globalSize.getWidth ());
cellFrameRect.size.height = static_cast<CGFloat> (globalSize.getHeight ());
if (!optionMenu->isPopupStyle ())
cellFrameRect.origin.y += cellFrameRect.size.height;
if (!multipleCheck && optionMenu->isCheckStyle ())
{
[[nsMenu itemWithTag:static_cast<NSInteger> (optionMenu->getCurrentIndex (true))]
setState:NSControlStateValueOn];
}
nsMenu.minimumWidth = cellFrameRect.size.width;
NSView* menuContainer = [[NSView alloc] initWithFrame:cellFrameRect];
[view addSubview:menuContainer];
NSMenuItem* selectedItem = nil;
if (optionMenu->isPopupStyle ())
selectedItem = [nsMenu itemAtIndex:optionMenu->getValue ()];
[nsMenu popUpMenuPositioningItem:selectedItem
atLocation:NSMakePoint (0, menuContainer.frame.size.height)
inView:menuContainer];
[menuContainer removeFromSuperviewWithoutNeedingDisplay];
[menuContainer release];
result.menu = (COptionMenu*)[nsMenu performSelector:@selector (selectedMenu)];
result.index = (int32_t) (intptr_t)[nsMenu performSelector:@selector (selectedItem)];
[nsMenu release];
callback (optionMenu, result);
}
} // VSTGUI
#endif // MAC_COCOA
@@ -0,0 +1,304 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include <objc/runtime.h>
#include <objc/message.h>
#include <tuple>
#include <string>
#include <optional>
#include <cmath>
#include <cassert>
//------------------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------------------
template<typename T>
struct ObjCVariable
{
ObjCVariable (__unsafe_unretained id obj, Ivar ivar) : obj (obj), ivar (ivar) {}
ObjCVariable (ObjCVariable&& o) { *this = std::move (o); }
ObjCVariable& operator= (ObjCVariable&& o)
{
obj = o.obj;
ivar = o.ivar;
o.obj = nullptr;
o.ivar = nullptr;
return *this;
}
T get () const
{
auto offset = ivar_getOffset (ivar);
return *reinterpret_cast<T*> (((__bridge uintptr_t)obj) + offset);
}
void set (const T& value)
{
auto offset = ivar_getOffset (ivar);
auto storage = reinterpret_cast<T*> (((__bridge uintptr_t)obj) + offset);
*storage = value;
}
private:
__unsafe_unretained id obj;
Ivar ivar {nullptr};
};
//------------------------------------------------------------------------------------
struct ObjCInstance
{
ObjCInstance (__unsafe_unretained id obj, Class superClass = nullptr) : obj (obj)
{
os.super_class = superClass;
}
template<typename T>
std::optional<ObjCVariable<T>> getVariable (const char* name) const
{
if (__strong auto ivar = class_getInstanceVariable (object_getClass (obj), name))
{
return {ObjCVariable<T> (obj, ivar)};
}
return {};
}
template<typename Func, typename... T>
void callSuper (SEL selector, T... args) const
{
void (*f) (__unsafe_unretained id, SEL, T...) =
(void (*) (__unsafe_unretained id, SEL, T...))objc_msgSendSuper;
f (getSuper (), selector, args...);
}
template<typename Func, typename R, typename... T>
R callSuper (SEL selector, T... args) const
{
R (*f)
(__unsafe_unretained id, SEL, T...) =
(R (*) (__unsafe_unretained id, SEL, T...))objc_msgSendSuper;
return f (getSuper (), selector, args...);
}
private:
id getSuper () const
{
if (os.receiver == nullptr)
{
os.receiver = obj;
}
if (os.super_class == nullptr)
{
os.super_class = class_getSuperclass (object_getClass (obj));
}
return (__bridge id) (&os);
}
__unsafe_unretained id obj;
mutable objc_super os {};
};
//------------------------------------------------------------------------
template<typename T>
struct RuntimeObjCClass
{
using Base = RuntimeObjCClass<T>;
static id alloc ()
{
static auto allocSel = sel_registerName ("alloc");
if (auto method = class_getClassMethod (instance ().cl, allocSel))
{
if (auto methodImpl = method_getImplementation (method))
{
using fn2 = id (*) (id, SEL);
auto alloc = (fn2)methodImpl;
return alloc (instance ().cl, allocSel);
}
}
return nil;
}
RuntimeObjCClass ()
{
cl = T::CreateClass ();
superClass = class_getSuperclass (cl);
}
virtual ~RuntimeObjCClass () noexcept
{
if (cl)
objc_disposeClassPair (cl);
}
static ObjCInstance makeInstance (__unsafe_unretained id obj)
{
return ObjCInstance (obj, instance ().superClass);
}
protected:
static T& instance ()
{
static T gInstance;
return gInstance;
}
Class getClass () const { return cl; }
Class getSuperClass () const { return superClass; }
private:
Class cl {nullptr};
Class superClass {nullptr};
};
//------------------------------------------------------------------------------------
struct ObjCClassBuilder
{
ObjCClassBuilder& init (const char* name, Class baseClass);
template<typename Func>
ObjCClassBuilder& addMethod (SEL selector, Func imp);
template<typename T>
ObjCClassBuilder& addIvar (const char* name);
ObjCClassBuilder& addProtocol (const char* name);
ObjCClassBuilder& addProtocol (Protocol* proto);
Class finalize ();
private:
static Class generateUniqueClass (const std::string& inClassName, Class baseClass);
template<typename Func>
ObjCClassBuilder& addMethod (SEL selector, Func imp, const char* types);
ObjCClassBuilder& addIvar (const char* name, size_t size, uint8_t alignment, const char* types);
template<typename R, typename... T>
static constexpr std::tuple<R, T...> functionArgs (R (*) (T...))
{
return std::tuple<R, T...> ();
}
template<typename... T>
static constexpr std::tuple<T...> functionArgs (void (*) (T...))
{
return std::tuple<T...> ();
}
template<typename R, typename... T>
static constexpr bool isVoidReturnType (R (*) (T...))
{
return false;
}
template<typename... T>
static constexpr bool isVoidReturnType (void (*) (T...))
{
return true;
}
template<typename Proc>
static std::string encodeFunction (Proc proc)
{
std::string result;
if (isVoidReturnType (proc))
result = "v";
std::apply ([&] (auto&&... args) { ((result += @encode (decltype (args))), ...); },
functionArgs (proc));
return result;
}
Class cl {nullptr};
Class baseClass {nullptr};
};
//------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::init (const char* name, Class bc)
{
baseClass = bc;
cl = generateUniqueClass (name, baseClass);
return *this;
}
//------------------------------------------------------------------------------------
inline Class ObjCClassBuilder::generateUniqueClass (const std::string& inClassName, Class baseClass)
{
std::string className (inClassName);
int32_t iteration = 0;
while (objc_lookUpClass (className.data ()) != nil)
{
iteration++;
className = inClassName + "_" + std::to_string (iteration);
}
Class resClass = objc_allocateClassPair (baseClass, className.data (), 0);
return resClass;
}
//-----------------------------------------------------------------------------
inline Class ObjCClassBuilder::finalize ()
{
objc_registerClassPair (cl);
auto res = cl;
baseClass = cl = nullptr;
return res;
}
//-----------------------------------------------------------------------------
template<typename Func>
inline ObjCClassBuilder& ObjCClassBuilder::addMethod (SEL selector, Func imp, const char* types)
{
auto res = class_addMethod (cl, selector, IMP (imp), types);
assert (res == true);
(void)res;
return *this;
}
//-----------------------------------------------------------------------------
template<typename Func>
ObjCClassBuilder& ObjCClassBuilder::addMethod (SEL selector, Func imp)
{
return addMethod (selector, imp, encodeFunction (imp).data ());
}
//------------------------------------------------------------------------
template<typename T>
inline ObjCClassBuilder& ObjCClassBuilder::addIvar (const char* name)
{
return addIvar (name, sizeof (T), static_cast<uint8_t> (std::log2 (sizeof (T))), @encode (T));
}
//-----------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::addIvar (const char* name, size_t size,
uint8_t alignment, const char* types)
{
auto res = class_addIvar (cl, name, size, alignment, types);
assert (res == true);
(void)res;
return *this;
}
//-----------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::addProtocol (const char* name)
{
if (auto protocol = objc_getProtocol (name))
return addProtocol (protocol);
return *this;
}
//-----------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::addProtocol (Protocol* proto)
{
auto res = class_addProtocol (cl, proto);
assert (res == true);
(void)res;
return *this;
}
//------------------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,121 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformgraphicsdevice.h"
#if TARGET_OS_IPHONE
#include <CoreGraphics/CoreGraphics.h>
#include <CoreText/CoreText.h>
#else
#include <ApplicationServices/ApplicationServices.h>
#endif
//------------------------------------------------------------------------
namespace VSTGUI {
class CoreGraphicsDevice;
//------------------------------------------------------------------------
class CoreGraphicsDeviceContext : public IPlatformGraphicsDeviceContext,
public IPlatformGraphicsDeviceContextBitmapExt,
public IPlatformGraphicsDeviceContextGradientExt
{
public:
CoreGraphicsDeviceContext (const CoreGraphicsDevice& device, void* cgContext);
~CoreGraphicsDeviceContext () noexcept override;
const IPlatformGraphicsDevice& getDevice () const override;
PlatformGraphicsPathFactoryPtr getGraphicsPathFactory () const override;
bool beginDraw () const override;
bool endDraw () const override;
// draw commands
bool drawLine (LinePair line) const override;
bool drawLines (const LineList& lines) const override;
bool drawPolygon (const PointList& polygonPointList,
PlatformGraphicsDrawStyle drawStyle) const override;
bool drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override;
bool drawArc (CRect rect, double startAngle1, double endAngle2,
PlatformGraphicsDrawStyle drawStyle) const override;
bool drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override;
bool drawPoint (CPoint point, CColor color) const override;
bool drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, double alpha,
BitmapInterpolationQuality quality) const override;
bool clearRect (CRect rect) const override;
bool drawGraphicsPath (IPlatformGraphicsPath& path, PlatformGraphicsPathDrawMode mode,
TransformMatrix* transformation) const override;
bool fillLinearGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient,
CPoint startPoint, CPoint endPoint, bool evenOdd,
TransformMatrix* transformation) const override;
bool fillRadialGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient,
CPoint center, CCoord radius, CPoint originOffset, bool evenOdd,
TransformMatrix* transformation) const override;
// state
void saveGlobalState () const override;
void restoreGlobalState () const override;
void setLineStyle (const CLineStyle& style) const override;
void setLineWidth (CCoord width) const override;
void setDrawMode (CDrawMode mode) const override;
void setClipRect (CRect clip) const override;
void setFillColor (CColor color) const override;
void setFrameColor (CColor color) const override;
void setGlobalAlpha (double newAlpha) const override;
void setTransformMatrix (const TransformMatrix& tm) const override;
// extension
const IPlatformGraphicsDeviceContextBitmapExt* asBitmapExt () const override;
// IPlatformGraphicsDeviceContextBitmapExt
bool drawBitmapNinePartTiled (IPlatformBitmap& bitmap, CRect dest,
const CNinePartTiledDescription& desc, double alpha,
BitmapInterpolationQuality quality) const override;
bool fillRectWithBitmap (IPlatformBitmap& bitmap, CRect srcRect, CRect dstRect, double alpha,
BitmapInterpolationQuality quality) const override;
// IPlatformGraphicsDeviceContextGradientExt
bool drawLinearGradientLine (const PointList& line, const IPlatformGradient& gradient,
CCoord lineWidth, LineCap lineCap,
LineJoin lineJoin) const override;
// private
void drawCTLine (CTLineRef line, CGPoint cgPoint, CTFontRef fontRef, CColor color,
bool underline, bool strikeThrough, bool antialias) const;
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
class CoreGraphicsBitmapContext : public CoreGraphicsDeviceContext
{
public:
using EndDrawFunc = std::function<void ()>;
CoreGraphicsBitmapContext (const CoreGraphicsDevice& device, void* cgContext, EndDrawFunc&& f);
bool endDraw () const override;
private:
EndDrawFunc endDrawFunc;
};
//------------------------------------------------------------------------
class CoreGraphicsDevice : public IPlatformGraphicsDevice
{
public:
PlatformGraphicsDeviceContextPtr
createBitmapContext (const PlatformBitmapPtr& bitmap) const override;
};
//------------------------------------------------------------------------
class CoreGraphicsDeviceFactory : public IPlatformGraphicsDeviceFactory
{
public:
PlatformGraphicsDevicePtr getDeviceForScreen (ScreenInfo::Identifier screen) const override;
};
//------------------------------------------------------------------------
} // VSTGUI
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../iplatformopenglview.h"
#if TARGET_OS_IPHONE
#if VSTGUI_OPENGL_SUPPORT
#ifdef __OBJC__
@class UIView, GLKView, NSRecursiveLock;
#else
struct GLKView;
struct UIView;
struct NSRecursiveLock;
#endif
namespace VSTGUI {
//------------------------------------------------------------------------------------
class GLKitOpenGLView : public IPlatformOpenGLView
{
public:
GLKitOpenGLView (UIView* parent);
~GLKitOpenGLView ();
bool init (IOpenGLView* view, PixelFormat* pixelFormat = nullptr) override;
void remove () override;
void invalidRect (const CRect& rect) override;
void viewSizeChanged (const CRect& visibleSize) override;
bool makeContextCurrent () override;
bool lockContext () override;
bool unlockContext () override;
void swapBuffers () override;
void doDraw (const CRect& r);
//------------------------------------------------------------------------------------
protected:
UIView* parent;
GLKView* platformView;
IOpenGLView* view;
NSRecursiveLock* lock;
PixelFormat pixelFormat;
};
} // VSTGUI
#endif // VSTGUI_OPENGL_SUPPORT
#endif // TARGET_OS_IPHONE
@@ -0,0 +1,185 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "uiopenglview.h"
#if TARGET_OS_IPHONE
#if VSTGUI_OPENGL_SUPPORT
#import <GLKit/GLKit.h>
#if __has_feature(objc_arc) && __clang_major__ >= 3
#define ARC_ENABLED 1
#endif // __has_feature(objc_arc)
@interface VSTGUI_GLKView : GLKView
{
VSTGUI::GLKitOpenGLView* view;
}
@end
//-----------------------------------------------------------------------------
@implementation VSTGUI_GLKView
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
- (id)initWithFrame:(CGRect)frame context:(EAGLContext *)context GLKitOpenGLView:(VSTGUI::GLKitOpenGLView*)_view
{
self = [super initWithFrame:frame context:context];
if (self)
{
view = _view;
}
return self;
}
//-----------------------------------------------------------------------------
- (void)drawRect:(CGRect)rect
{
VSTGUI::CRect r (rect.origin.x, rect.origin.y, 0, 0);
r.setWidth (rect.size.width);
r.setHeight (rect.size.height);
view->doDraw (r);
}
@end
namespace VSTGUI {
//-----------------------------------------------------------------------------
GLKitOpenGLView::GLKitOpenGLView (UIView* parent)
: parent (parent)
, platformView (nil)
, view (nullptr)
, lock (nil)
{
}
//-----------------------------------------------------------------------------
GLKitOpenGLView::~GLKitOpenGLView ()
{
}
//-----------------------------------------------------------------------------
bool GLKitOpenGLView::init (IOpenGLView* _view, PixelFormat* _pixelFormat)
{
if (platformView || parent == nil)
return false;
EAGLContext* glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
if (glContext == nil)
return false;
#if !ARC_ENABLED
[glContext autorelease];
#endif
lock = [NSRecursiveLock new];
CGRect r = CGRectMake (0, 0, 100, 100);
platformView = [[VSTGUI_GLKView alloc] initWithFrame:r context:glContext GLKitOpenGLView:this];
if (platformView == nil)
return false;
if (_pixelFormat)
{
pixelFormat = *_pixelFormat;
}
if (pixelFormat.kMultiSample)
platformView.drawableMultisample = GLKViewDrawableMultisample4X;
if (pixelFormat.depthSize == 16)
platformView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
else if (pixelFormat.depthSize == 24)
platformView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
return true;
}
//-----------------------------------------------------------------------------
void GLKitOpenGLView::remove ()
{
if (platformView)
{
[platformView removeFromSuperview];
#if !ARC_ENABLED
[platformView release];
[lock release];
#endif
platformView = nil;
view = nullptr;
lock = nil;
}
}
//-----------------------------------------------------------------------------
void GLKitOpenGLView::invalidRect (const CRect& rect)
{
if (platformView)
{
CGRect r = CGRectMake (rect.left, rect.top, rect.getWidth (), rect.getHeight ());
[platformView setNeedsDisplayInRect:r];
}
}
//-----------------------------------------------------------------------------
void GLKitOpenGLView::viewSizeChanged (const CRect& visibleSize)
{
if (platformView)
{
[lock lock];
CGRect r = CGRectMake (visibleSize.left, visibleSize.top, visibleSize.getWidth (), visibleSize.getHeight ());
platformView.frame = r;
if ([platformView superview] == nil)
{
[parent addSubview:platformView];
}
[lock unlock];
}
}
//-----------------------------------------------------------------------------
bool GLKitOpenGLView::makeContextCurrent ()
{
if (platformView)
{
return [EAGLContext setCurrentContext:platformView.context] ? true : false;
}
return false;
}
//-----------------------------------------------------------------------------
bool GLKitOpenGLView::lockContext ()
{
[lock lock];
return true;
}
//-----------------------------------------------------------------------------
bool GLKitOpenGLView::unlockContext ()
{
[lock unlock];
return true;
}
//-----------------------------------------------------------------------------
void GLKitOpenGLView::swapBuffers ()
{
}
//-----------------------------------------------------------------------------
void GLKitOpenGLView::doDraw (const CRect& r)
{
if (view)
{
lockContext ();
view->drawOpenGL (r);
unlockContext ();
}
}
} // VSTGUI
#endif // VSTGUI_OPENGL_SUPPORT
#endif // TARGET_OS_IPHONE
@@ -0,0 +1,42 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../iplatformtextedit.h"
#if TARGET_OS_IPHONE
#ifdef __OBJC__
@class UIView;
@class UITextField;
@class VSTGUI_UITextFieldDelegate;
#else
struct UIView;
struct UITextField;
struct VSTGUI_UITextFieldDelegate;
#endif
namespace VSTGUI {
class UITextEdit : public IPlatformTextEdit
{
public:
UITextEdit (UIView* parent, IPlatformTextEditCallback* textEdit);
~UITextEdit ();
UTF8String getText () override;
bool setText (const UTF8String& text) override;
bool updateSize () override;
bool drawsPlaceholder () const override { return true; }
protected:
UITextField* platformControl;
VSTGUI_UITextFieldDelegate* delegate;
UIView* parent;
};
} // VSTGUI
#endif // TARGET_OS_IPHONE
@@ -0,0 +1,182 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "uitextedit.h"
#import <UIKit/UIKit.h>
#if TARGET_OS_IPHONE
#import "../cfontmac.h"
#import "../macglobals.h"
#import "../macstring.h"
#if __has_feature(objc_arc) && __clang_major__ >= 3
#define ARC_ENABLED 1
#endif // __has_feature(objc_arc)
//------------------------------------------------------------------------------------
@interface VSTGUI_UITextFieldDelegate : NSObject<UITextFieldDelegate>
//------------------------------------------------------------------------------------
{
VSTGUI::IPlatformTextEditCallback* textEdit;
}
@end
//------------------------------------------------------------------------------------
@implementation VSTGUI_UITextFieldDelegate
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
- (id)initWithUITextEdit:(VSTGUI::IPlatformTextEditCallback*)_textEdit
{
self = [super init];
if (self)
textEdit = _textEdit;
return self;
}
//------------------------------------------------------------------------------------
- (void)looseFocus
{
if (textEdit)
{
textEdit->platformLooseFocus (false);
}
}
//------------------------------------------------------------------------------------
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
if (textEdit)
{
[self performSelector:@selector(looseFocus) withObject:nil afterDelay:0];
}
return YES;
}
//------------------------------------------------------------------------------------
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
VSTGUI::IPlatformTextEditCallback* tmp = textEdit;
textEdit = 0;
tmp->platformLooseFocus (true);
return YES;
}
@end
//------------------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------------------
UITextEdit::UITextEdit (UIView* parent, IPlatformTextEditCallback* textEdit)
: IPlatformTextEdit (textEdit)
, platformControl (0)
, parent (parent)
{
delegate = [[VSTGUI_UITextFieldDelegate alloc] initWithUITextEdit:textEdit];
CRect rect (textEdit->platformGetSize ());
CPoint textInset = textEdit->platformGetTextInset ();
CGRect r = CGRectFromCRect (rect);
r.origin.x += textInset.x / 2.;
r.origin.y += textInset.y / 2.;
r.size.width -= textInset.x / 2;
r.size.height -= textInset.y / 2;
platformControl = [[UITextField alloc] initWithFrame:r];
bool fontSet = false;
CoreTextFont* ctf = textEdit->platformGetFont ()->getPlatformFont ().cast<CoreTextFont> ();
if (ctf)
{
CTFontRef fontRef = ctf->getFontRef ();
if (fontRef)
{
CTFontDescriptorRef fontDesc = CTFontCopyFontDescriptor (fontRef);
[platformControl setFont:[UIFont fontWithDescriptor:(__bridge UIFontDescriptor *)fontDesc size:0]];
CFRelease (fontDesc);
fontSet = true;
}
}
if (!fontSet)
{
NSString* fontName = [NSString stringWithCString:textEdit->platformGetFont ()->getName () encoding:NSUTF8StringEncoding];
[platformControl setFont:[UIFont fontWithName:fontName size:static_cast<CGFloat> (textEdit->platformGetFont ()->getSize ())]];
}
CColor fontColor = textEdit->platformGetFontColor ();
platformControl.textColor = [UIColor colorWithRed:fontColor.red / 255.f green:fontColor.green / 255.f blue:fontColor.red / 255.f alpha:fontColor.alpha / 255.f];
platformControl.borderStyle = UITextBorderStyleNone;
platformControl.opaque = NO;
platformControl.clearsContextBeforeDrawing = YES;
NSTextAlignment textAlignment;
switch (textEdit->platformGetHoriTxtAlign ())
{
case kLeftText: textAlignment = NSTextAlignmentLeft; break;
case kCenterText: textAlignment = NSTextAlignmentCenter; break;
case kRightText:textAlignment = NSTextAlignmentRight; break;
}
platformControl.textAlignment = textAlignment;
platformControl.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
platformControl.clearButtonMode = UITextFieldViewModeNever;
platformControl.returnKeyType = UIReturnKeyDefault;
platformControl.enablesReturnKeyAutomatically = YES;
platformControl.delegate = delegate;
setText (textEdit->platformGetText ());
[parent addSubview:platformControl];
[platformControl becomeFirstResponder];
}
//------------------------------------------------------------------------------------
UITextEdit::~UITextEdit ()
{
if (platformControl)
{
[platformControl removeFromSuperview];
#if !ARC_ENABLED
[platformControl release];
#endif
platformControl = nil;
}
#if !ARC_ENABLED
[delegate release];
#endif
delegate = nil;
}
//------------------------------------------------------------------------------------
UTF8String UITextEdit::getText ()
{
if (platformControl)
{
NSString* text = [platformControl text];
return [text UTF8String];
}
return 0;
}
//------------------------------------------------------------------------------------
bool UITextEdit::setText (const UTF8String& text)
{
if (platformControl == nullptr)
return false;
if (NSString* t = fromUTF8String<NSString*> (text))
{
[platformControl setText:t];
return true;
}
return false;
}
//------------------------------------------------------------------------------------
bool UITextEdit::updateSize ()
{
return false;
}
} // VSTGUI
#endif
@@ -0,0 +1,36 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../itouchevent.h"
#if VSTGUI_TOUCH_EVENT_HANDLING
#ifdef __OBJC__
@class UITouch;
#else
struct UITouch;
#endif
namespace VSTGUI {
class UITouchEvent : public ITouchEvent
{
public:
typedef std::map<UITouch*, int32_t> NativeTouches;
int32_t touchCounter;
double currentTime;
NativeTouches nativeTouches;
UITouchEvent () : touchCounter (0) {}
TouchMap& getTouchMap () { return touches; }
double getTimeStamp () const override { return currentTime; }
};
} // VSTGUI
#endif // VSTGUI_TOUCH_EVENT_HANDLING
@@ -0,0 +1,67 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../vstguifwd.h"
#include "../../../cview.h"
#include "../../iplatformframe.h"
#if TARGET_OS_IPHONE
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
struct UIView;
#endif
namespace VSTGUI {
//-----------------------------------------------------------------------------
class UIViewFrame : public IPlatformFrame
{
public:
UIViewFrame (IPlatformFrameCallback* frame, const CRect& size, UIView* parent);
~UIViewFrame ();
UIView* getPlatformControl () const { return uiView; }
IPlatformFrameCallback* getFrame () const { return frame; }
// IPlatformFrame
bool getGlobalPosition (CPoint& pos) const override;
bool setSize (const CRect& newSize) override;
bool getSize (CRect& size) const override;
bool getCurrentMousePosition (CPoint& mousePosition) const override { return false; };
bool getCurrentMouseButtons (CButtonState& buttons) const override { return false; };
bool getCurrentModifiers (Modifiers& modifiers) const override { return false; }
bool setMouseCursor (CCursorType type) override { return false; };
bool invalidRect (const CRect& rect) override;
bool scrollRect (const CRect& src, const CPoint& distance) override;
bool showTooltip (const CRect& rect, const char* utf8Text) override { return false; };
bool hideTooltip () override { return false; };
void* getPlatformRepresentation () const override { return (__bridge void*)uiView; }
SharedPointer<IPlatformTextEdit> createPlatformTextEdit (IPlatformTextEditCallback* textEdit) override;
SharedPointer<IPlatformOptionMenu> createPlatformOptionMenu () override;
#if VSTGUI_OPENGL_SUPPORT
SharedPointer<IPlatformOpenGLView> createPlatformOpenGLView () override;
#endif
SharedPointer<IPlatformViewLayer> createPlatformViewLayer (IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer) override;
#if VSTGUI_ENABLE_DEPRECATED_METHODS
DragResult doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) override;
#endif
bool doDrag (const DragDescription& dragDescription, const SharedPointer<IDragCallback>& callback) override;
PlatformType getPlatformType () const override { return PlatformType::kUIView; }
void onFrameClosed () override {}
Optional<UTF8String> convertCurrentKeyEventToText () override { return {}; }
bool setupGenericOptionMenu (bool use, GenericOptionMenuTheme* theme = nullptr) override { return false; }
//-----------------------------------------------------------------------------
protected:
UIView* uiView;
};
} // VSTGUI
#endif // TARGET_OS_IPHONE
@@ -0,0 +1,294 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "uiviewframe.h"
#if TARGET_OS_IPHONE
#import "../../../cfileselector.h"
#import "../../../idatapackage.h"
#import "../../iplatformoptionmenu.h"
#import "../coregraphicsdevicecontext.h"
#import "../cgbitmap.h"
#import "../quartzgraphicspath.h"
#import "../caviewlayer.h"
#import "uitouchevent.h"
#import "uitextedit.h"
#import "uiopenglview.h"
#import <vector>
#if __has_feature(objc_arc) && __clang_major__ >= 3
#define ARC_ENABLED 1
#endif // __has_feature(objc_arc)
using namespace VSTGUI;
//-----------------------------------------------------------------------------
@interface VSTGUI_UIView : UIView
//-----------------------------------------------------------------------------
{
UIViewFrame* uiViewFrame;
UITouchEvent touchEvent;
}
- (id)initWithUIViewFrame:(UIViewFrame*)viewFrame parent:(UIView*)parent size:(const CRect*)size;
@end
//-----------------------------------------------------------------------------
@implementation VSTGUI_UIView
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
- (id)initWithUIViewFrame:(UIViewFrame*)viewFrame parent:(UIView*)parent size:(const CRect*)size
{
self = [super initWithFrame:CGRectFromCRect (*size)];
if (self)
{
self.multipleTouchEnabled = YES;
self.exclusiveTouch = YES;
uiViewFrame = viewFrame;
[parent addSubview:self];
}
return self;
}
//-----------------------------------------------------------------------------
- (void)drawRect:(CGRect)rect
{
auto device = getPlatformFactory ().getGraphicsDeviceFactory ().getDeviceForScreen (
DefaultScreenIdentifier);
if (!device)
return;
auto cgDevice = std::static_pointer_cast<CoreGraphicsDevice> (device);
auto deviceContext = std::make_shared<CoreGraphicsDeviceContext> (
*cgDevice.get (), UIGraphicsGetCurrentContext ());
uiViewFrame->getFrame ()->platformDrawRects (deviceContext, self.layer.contentsScale,
{CRectFromCGRect (rect)});
}
//-----------------------------------------------------------------------------
- (BOOL)canBecomeFirstResponder
{
return YES;
}
//-----------------------------------------------------------------------------
- (void)didMoveToWindow
{
uiViewFrame->getFrame ()->platformOnActivate (self.window ? true : false);
}
//-----------------------------------------------------------------------------
- (void)updateTouchEvent:(NSSet*)touches
{
ITouchEvent::TouchMap& touchMap = touchEvent.getTouchMap ();
for (UITouch* touch in touches)
{
auto it = touchEvent.nativeTouches.find (touch);
if (it != touchEvent.nativeTouches.end ())
{
auto iTouch = touchMap.find (it->second);
vstgui_assert (iTouch != touchMap.end ());
if (touch.phase == UITouchPhaseStationary)
{
iTouch->second.state = ITouchEvent::kNoChange;
}
else
{
iTouch->second.location = CPointFromCGPoint ([touch locationInView:self]);
switch (touch.phase)
{
case UITouchPhaseMoved: iTouch->second.state = ITouchEvent::kMoved; break;
case UITouchPhaseEnded: iTouch->second.state = ITouchEvent::kEnded; break;
case UITouchPhaseCancelled: iTouch->second.state = ITouchEvent::kCanceled; break;
default: iTouch->second.state = ITouchEvent::kNoChange; break;
}
}
}
else if (touch.phase == UITouchPhaseBegan)
{
int32_t touchID = touchEvent.touchCounter++;
touchEvent.nativeTouches.insert (std::make_pair (touch, touchID));
ITouchEvent::Touch t;
t.timeStamp = touchEvent.currentTime;
t.state = ITouchEvent::kBegan;
t.location = CPointFromCGPoint ([touch locationInView:self]);
t.tapCount = static_cast<uint32_t> (touch.tapCount);
touchMap.insert (std::make_pair (touchID, t));
}
}
}
//-----------------------------------------------------------------------------
- (void)removeTouches:(NSSet*)touches
{
ITouchEvent::TouchMap& touchMap = touchEvent.getTouchMap ();
for (UITouch* touch in touches)
{
auto it = touchEvent.nativeTouches.find (touch);
vstgui_assert (it != touchEvent.nativeTouches.end ());
if (it != touchEvent.nativeTouches.end ())
{
touchMap.erase (it->second);
touchEvent.nativeTouches.erase (it);
}
}
}
//-----------------------------------------------------------------------------
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
if (event.type == UIEventTypeTouches)
{
touchEvent.currentTime = event.timestamp;
[self updateTouchEvent:[event allTouches]];
uiViewFrame->getFrame()->platformOnTouchEvent (touchEvent);
}
}
//-----------------------------------------------------------------------------
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
if (event.type == UIEventTypeTouches)
{
touchEvent.currentTime = event.timestamp;
[self updateTouchEvent:[event allTouches]];
uiViewFrame->getFrame()->platformOnTouchEvent (touchEvent);
}
}
//-----------------------------------------------------------------------------
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
if (event.type == UIEventTypeTouches)
{
touchEvent.currentTime = event.timestamp;
[self updateTouchEvent:[event allTouches]];
uiViewFrame->getFrame()->platformOnTouchEvent (touchEvent);
[self removeTouches:touches];
}
}
//-----------------------------------------------------------------------------
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
if (event.type == UIEventTypeTouches)
{
touchEvent.currentTime = event.timestamp;
[self updateTouchEvent:[event allTouches]];
uiViewFrame->getFrame()->platformOnTouchEvent (touchEvent);
[self removeTouches:touches];
}
}
@end
namespace VSTGUI {
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
UIViewFrame::UIViewFrame (IPlatformFrameCallback* frame, const CRect& size, UIView* parent)
: IPlatformFrame (frame)
, uiView (nullptr)
{
uiView = [[VSTGUI_UIView alloc] initWithUIViewFrame:this parent:parent size:&size];
}
//-----------------------------------------------------------------------------
UIViewFrame::~UIViewFrame ()
{
[uiView removeFromSuperview];
#if !ARC_ENABLED
[uiView release];
#endif
}
//-----------------------------------------------------------------------------
bool UIViewFrame::getGlobalPosition (CPoint& pos) const
{
if (uiView)
{
CGPoint p = [uiView convertPoint:[uiView bounds].origin toView:nil];
pos.x = p.x;
pos.y = p.y;
// TODO: check if this is correct in all cases
return true;
}
return false;
}
//-----------------------------------------------------------------------------
bool UIViewFrame::setSize (const CRect& newSize)
{
if (uiView)
{
uiView.frame = CGRectFromCRect (newSize);
return true;
}
return false;
}
//-----------------------------------------------------------------------------
bool UIViewFrame::getSize (CRect& size) const
{
if (uiView)
{
size = CRectFromCGRect (uiView.frame);
return true;
}
return false;
}
//-----------------------------------------------------------------------------
bool UIViewFrame::invalidRect (const CRect& rect)
{
[uiView setNeedsDisplayInRect:CGRectFromCRect (rect)];
return true;
}
//-----------------------------------------------------------------------------
bool UIViewFrame::scrollRect (const CRect& src, const CPoint& distance)
{
return false;
}
//-----------------------------------------------------------------------------
SharedPointer<IPlatformTextEdit> UIViewFrame::createPlatformTextEdit (IPlatformTextEditCallback* textEdit)
{
return owned <IPlatformTextEdit> (new UITextEdit (uiView, textEdit));
}
//-----------------------------------------------------------------------------
SharedPointer<IPlatformOptionMenu> UIViewFrame::createPlatformOptionMenu ()
{
return nullptr;
}
#if VSTGUI_OPENGL_SUPPORT
//-----------------------------------------------------------------------------
SharedPointer<IPlatformOpenGLView> UIViewFrame::createPlatformOpenGLView ()
{
return owned<IPlatformOpenGLView> (new GLKitOpenGLView (uiView));
}
#endif
//-----------------------------------------------------------------------------
SharedPointer<IPlatformViewLayer> UIViewFrame::createPlatformViewLayer (IPlatformViewLayerDelegate* drawDelegate, IPlatformViewLayer* parentLayer)
{
CAViewLayer* parentViewLayer = dynamic_cast<CAViewLayer*> (parentLayer);
auto layer = owned (new CAViewLayer (parentViewLayer ? parentViewLayer->getCALayer () : [uiView layer]));
layer->init (drawDelegate);
return shared<IPlatformViewLayer> (layer);
}
#if VSTGUI_ENABLE_DEPRECATED_METHODS
DragResult UIViewFrame::doDrag (IDataPackage* source, const CPoint& offset, CBitmap* dragBitmap) { return kDragError; }
#endif
bool UIViewFrame::doDrag (const DragDescription& dragDescription, const SharedPointer<IDragCallback>& callback) { return false; }
}
#endif // TARGET_OS_IPHONE
@@ -0,0 +1,25 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../vstguibase.h"
#ifdef __OBJC__
@class NSPasteboard;
#else
struct NSPasteboard;
#endif
namespace VSTGUI {
class IDataPackage;
namespace MacClipboard {
extern SharedPointer<IDataPackage> createClipboardDataPackage ();
extern SharedPointer<IDataPackage> createDragDataPackage (NSPasteboard* pasteboard);
extern void setClipboard (const SharedPointer<IDataPackage>& data);
extern const char* getPasteboardBinaryType ();
}} // namespaces
@@ -0,0 +1,360 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "macclipboard.h"
#import "macglobals.h"
#import "../../cdropsource.h"
#import <Cocoa/Cocoa.h>
#import <vector>
#import <string>
#ifndef MAC_OS_X_VERSION_10_14
#define MAC_OS_X_VERSION_10_14 101400
#endif
namespace VSTGUI {
namespace MacClipboard {
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14
//------------------------------------------------------------------------
class Pasteboard : public IDataPackage
{
public:
Pasteboard (NSPasteboard* pb) : pb (pb) { entries.resize ([pb pasteboardItems].count); }
uint32_t getCount () const override { return static_cast<uint32_t> (entries.size ()); }
uint32_t getDataSize (uint32_t index) const override
{
if (index >= getCount ())
return 0;
prepareEntryAtIndex (index);
return static_cast<uint32_t> (entries[index].data.size ());
}
Type getDataType (uint32_t index) const override
{
if (index >= getCount ())
return Type::kError;
prepareEntryAtIndex (index);
return entries[index].type;
}
uint32_t getData (uint32_t index, const void*& buffer, Type& type) const override
{
if (index >= getCount ())
return 0;
prepareEntryAtIndex (index);
buffer = entries[index].data.data ();
type = entries[index].type;
return static_cast<uint32_t> (entries[index].data.size ());
}
private:
struct Entry
{
std::vector<uint8_t> data;
Type type {Type::kError};
};
void prepareEntryAtIndex (uint32_t index) const
{
if (entries[index].type == Type::kError)
entries[index] = makeEntry (pb.pasteboardItems[index]);
}
static Entry makeEntry (NSPasteboardItem* item)
{
auto DefaultPBItemTypes =
@[NSPasteboardTypeString, NSPasteboardTypeFileURL, NSPasteboardTypeColor];
Entry result;
if (auto availableType = [item availableTypeFromArray:DefaultPBItemTypes])
{
if ([availableType isEqualToString:NSPasteboardTypeFileURL])
{
result.type = Type::kFilePath;
NSString* fileUrlStr = [item stringForType:NSPasteboardTypeFileURL];
NSURL* url = [NSURL URLWithString:fileUrlStr];
std::string pathStr = url.path.UTF8String;
result.data.resize (pathStr.size ());
memcpy (result.data.data (), pathStr.data (), pathStr.size ());
}
else if ([availableType isEqualToString:NSPasteboardTypeColor])
{
result.type = Type::kText;
if (NSData* nsColorData = [item dataForType:NSPasteboardTypeColor])
{
if (NSColor* nsColor =
[NSKeyedUnarchiver unarchivedObjectOfClass:[NSColor class]
fromData:nsColorData
error:nil])
{
nsColor = [nsColor
colorUsingColorSpace:[[[NSColorSpace alloc]
initWithCGColorSpace:GetCGColorSpace ()]
autorelease]];
int32_t red = static_cast<int32_t> ([nsColor redComponent] * 255.);
int32_t green = static_cast<int32_t> ([nsColor greenComponent] * 255.);
int32_t blue = static_cast<int32_t> ([nsColor blueComponent] * 255.);
int32_t alpha = static_cast<int32_t> ([nsColor alphaComponent] * 255.);
char str[10];
snprintf (str, 10, "#%02x%02x%02x%02x", red, green, blue, alpha);
result.data.resize (10);
memcpy (result.data.data (), str, 10);
}
}
}
else
{
assert ([availableType isEqualToString:NSPasteboardTypeString]);
result.type = Type::kText;
if (auto data = [item dataForType:availableType])
{
result.data.resize (data.length);
memcpy (result.data.data (), data.bytes, data.length);
}
}
}
else
{
result.type = Type::kBinary;
auto data = [item dataForType:item.types[0]];
result.data.resize (data.length);
memcpy (result.data.data (), data.bytes, data.length);
}
return result;
}
NSPasteboard* pb;
mutable std::vector<Entry> entries;
};
#else
//-----------------------------------------------------------------------------
class Pasteboard : public IDataPackage
{
public:
Pasteboard (NSPasteboard* pb);
~Pasteboard () noexcept override;
uint32_t getCount () const override;
uint32_t getDataSize (uint32_t index) const override;
Type getDataType (uint32_t index) const override;
uint32_t getData (uint32_t index, const void*& buffer, Type& type) const override;
protected:
NSPasteboard* pb;
uint32_t nbItems;
bool stringsAreFiles;
std::vector<std::string> strings;
NSMutableArray* dataArray;
};
//-----------------------------------------------------------------------------
Pasteboard::Pasteboard (NSPasteboard* pb)
: pb (pb)
, nbItems (0)
, stringsAreFiles (false)
, dataArray (nullptr)
{
NSArray *supportedTypes = [NSArray arrayWithObjects: NSStringPboardType, nil];
NSString* hasString = [pb availableTypeFromArray: supportedTypes];
if (hasString)
{
nbItems = 1;
NSString* unicodeText = [pb stringForType:NSStringPboardType];
if (unicodeText)
{
strings.emplace_back ([unicodeText UTF8String]);
}
}
else
{
supportedTypes = [NSArray arrayWithObjects: NSFilenamesPboardType, nil];
NSString* hasFilenames = [pb availableTypeFromArray: supportedTypes];
if (hasFilenames)
{
stringsAreFiles = true;
NSArray* fileNames = [pb propertyListForType:hasFilenames];
nbItems = static_cast<uint32_t> ([fileNames count]);
for (uint32_t i = 0; i < nbItems; i++)
{
NSString* str = [fileNames objectAtIndex:i];
if (str)
strings.emplace_back ([str UTF8String]);
}
}
else if ([pb availableTypeFromArray:[NSArray arrayWithObject:NSColorPboardType]])
{
NSColor* nsColor = [NSColor colorFromPasteboard:pb];
if (nsColor)
{
nsColor = [nsColor colorUsingColorSpace:[[[NSColorSpace alloc] initWithCGColorSpace:GetCGColorSpace ()] autorelease]];
int32_t red = static_cast<int32_t> ([nsColor redComponent] * 255.);
int32_t green = static_cast<int32_t> ([nsColor greenComponent] * 255.);
int32_t blue = static_cast<int32_t> ([nsColor blueComponent] * 255.);
int32_t alpha = static_cast<int32_t> ([nsColor alphaComponent] * 255.);
char str[10];
snprintf (str, 10, "#%02x%02x%02x%02x", red, green, blue, alpha);
strings.emplace_back (str);
}
}
else
{
nbItems = static_cast<uint32_t> ([[pb types] count]);
dataArray = [[NSMutableArray alloc] initWithCapacity:nbItems];
for (uint32_t i = 0; i < nbItems; i++)
{
NSData* nsData = [pb dataForType:[[pb types] objectAtIndex:i]];
[dataArray addObject:nsData];
}
}
}
}
//-----------------------------------------------------------------------------
Pasteboard::~Pasteboard () noexcept
{
if (dataArray)
[dataArray release];
}
//-----------------------------------------------------------------------------
uint32_t Pasteboard::getCount () const
{
return nbItems;
}
//-----------------------------------------------------------------------------
uint32_t Pasteboard::getDataSize (uint32_t index) const
{
if (dataArray)
{
return static_cast<uint32_t> ([[dataArray objectAtIndex:index] length]);
}
if (index < strings.size ())
{
return static_cast<uint32_t> (strings[index].length ());
}
return 0;
}
//-----------------------------------------------------------------------------
Pasteboard::Type Pasteboard::getDataType (uint32_t index) const
{
if (dataArray)
return kBinary;
else if (index < strings.size ())
{
if (stringsAreFiles)
return kFilePath;
return kText;
}
return kError;
}
//-----------------------------------------------------------------------------
uint32_t Pasteboard::getData (uint32_t index, const void*& buffer, Pasteboard::Type& type) const
{
if (dataArray)
{
buffer = [[dataArray objectAtIndex:index] bytes];
type = kBinary;
return static_cast<uint32_t> ([[dataArray objectAtIndex:index] length]);
}
if (index < strings.size ())
{
buffer = strings[index].c_str ();
type = stringsAreFiles ? kFilePath : kText;
return static_cast<uint32_t> (strings[index].length ());
}
return 0;
}
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
SharedPointer<IDataPackage> createClipboardDataPackage ()
{
return makeOwned<Pasteboard> ([NSPasteboard generalPasteboard]);
}
//-----------------------------------------------------------------------------
SharedPointer<IDataPackage> createDragDataPackage (NSPasteboard* pasteboard)
{
return makeOwned<Pasteboard> (pasteboard);
}
//-----------------------------------------------------------------------------
void setClipboard (const SharedPointer<IDataPackage>& dataSource)
{
NSPasteboard* pb = [NSPasteboard generalPasteboard];
if (dataSource)
{
[pb clearContents];
uint32_t nbItems = dataSource->getCount ();
NSMutableArray* fileArray = nullptr;
IDataPackage::Type type;
const void* data;
uint32_t length;
for (uint32_t i = 0; i < nbItems; i++)
{
if ((length = dataSource->getData (i, data, type)) > 0)
{
switch (type)
{
case IDataPackage::kBinary:
{
[pb declareTypes:[NSArray arrayWithObject:[NSString stringWithCString:MacClipboard::getPasteboardBinaryType () encoding:NSASCIIStringEncoding]] owner:nil];
[pb setData:[NSData dataWithBytes:data length:length] forType:[NSString stringWithCString:MacClipboard::getPasteboardBinaryType () encoding:NSASCIIStringEncoding]];
return;
}
case IDataPackage::kText:
{
[pb declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil];
[pb setString:[[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease] forType:NSPasteboardTypeString];
return;
}
case IDataPackage::kFilePath:
{
if (fileArray == nullptr)
fileArray = [[[NSMutableArray alloc] init] autorelease];
auto fileStr =
[NSString stringWithUTF8String:static_cast<const char*> (data)];
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14
[fileArray addObject:[NSURL fileURLWithPath:fileStr]];
#else
[fileArray addObject:fileStr];
#endif
break;
}
case IDataPackage::kError:
{
continue;
}
}
}
}
if (fileArray)
{
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14
[pb writeObjects:fileArray];
#else
[pb declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
[pb setPropertyList:fileArray forType:NSFilenamesPboardType];
#endif
}
}
else
{
[pb clearContents];
}
}
//-----------------------------------------------------------------------------
const char* getPasteboardBinaryType ()
{
return "net.sourceforge.vstgui.pasteboard.type.binary";
}
}} // namespaces
@@ -0,0 +1,152 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../platformfactory.h"
typedef struct __CFBundle* CFBundleRef;
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
class MacFactory final : public IPlatformFactory
{
public:
MacFactory (CFBundleRef bundle);
~MacFactory () noexcept override;
CFBundleRef getBundle () const noexcept;
void setUseAsynchronousLayerDrawing (bool state) const noexcept;
bool getUseAsynchronousLayerDrawing () const noexcept;
void enableVisualizeRedrawAreas (bool state) const noexcept;
bool enableVisualizeRedrawAreas () const noexcept;
void finalize () noexcept final;
/** Return platform ticks (millisecond resolution)
* @return ticks
*/
uint64_t getTicks () const noexcept final;
/** Create a platform frame object
* @param frame callback
* @param size size
* @param parent platform parent object
* @param parentType type of platform parent object
* @param config optional config object
* @return platform frame or nullptr on failure
*/
PlatformFramePtr createFrame (IPlatformFrameCallback* frame, const CRect& size, void* parent,
PlatformType parentType,
IPlatformFrameConfig* config = nullptr) const noexcept final;
/** Create a platform font object
* @param name name of the font
* @param size font size
* @param style font style
* @return platform font or nullptr on failure
*/
PlatformFontPtr createFont (const UTF8String& name, const CCoord& size,
const int32_t& style) const noexcept final;
/** Query all platform font families
* @param callback callback called for every font
* @return true on success
*/
bool getAllFontFamilies (const FontFamilyCallback& callback) const noexcept final;
/** Create an empty platform bitmap object
* @param size size of the bitmap
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmap (const CPoint& size) const noexcept final;
/** Create a platform bitmap object from a resource description
* @param desc description where to find the bitmap
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmap (const CResourceDescription& desc) const noexcept final;
/** Create a platform bitmap object from a file
* @param absolutePath the absolute path of the bitmap file location
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmapFromPath (UTF8StringPtr absolutePath) const noexcept final;
/** Create a platform bitmap object from memory
* @param ptr memory location
* @param memSize memory size
* @return platform bitmap or nullptr on failure
*/
PlatformBitmapPtr createBitmapFromMemory (const void* ptr,
uint32_t memSize) const noexcept final;
/** Create a memory representation of the platform bitmap in PNG format.
* @param bitmap the platform bitmap object
* @return memory buffer containing the PNG representation of the bitmap
*/
PNGBitmapBuffer
createBitmapMemoryPNGRepresentation (const PlatformBitmapPtr& bitmap) const noexcept final;
/** Create a platform resource input stream
* @param desc description where to find the file to open
* @return platform resource input stream or nullptr if not found
*/
PlatformResourceInputStreamPtr
createResourceInputStream (const CResourceDescription& desc) const noexcept final;
/** Create a platform string object
* @param utf8String optional initial UTF-8 encoded string
* @return platform string object or nullptr on failure
*/
PlatformStringPtr createString (UTF8StringPtr utf8String = nullptr) const noexcept final;
/** Create a platform timer object
* @param callback timer callback object
* @return platform timer object or nullptr on failure
*/
PlatformTimerPtr createTimer (IPlatformTimerCallback* callback) const noexcept final;
/** Set clipboard data
* @param data data to put on the clipboard
* @return true on success
*/
bool setClipboard (const DataPackagePtr& data) const noexcept final;
/** Get clipboard data
* @return data package pointer
*/
DataPackagePtr getClipboard () const noexcept final;
/** Create a platform gradient object
* @return platform gradient object or nullptr on failure
*/
PlatformGradientPtr createGradient () const noexcept final;
/** Create a platform file selector
* @param style file selector style
* @param frame frame
* @return platform file selector or nullptr on failure
*/
PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style,
IPlatformFrame* frame) const noexcept final;
/** Get the graphics device factory
*
* @return platform graphics device factory
*/
const IPlatformGraphicsDeviceFactory& getGraphicsDeviceFactory () const noexcept final;
const IPlatformTaskExecutor& getTaskExecutor () const noexcept final;
bool replaceTaskExecutor (const ReplaceTaskExecFunc& replaceFunc) const noexcept final;
const LinuxFactory* asLinuxFactory () const noexcept final;
const MacFactory* asMacFactory () const noexcept final;
const Win32Factory* asWin32Factory () const noexcept final;
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,279 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../common/fileresourceinputstream.h"
#include "../iplatformfont.h"
#include "../iplatformframe.h"
#include "../iplatformframecallback.h"
#include "../iplatformresourceinputstream.h"
#include "../iplatformstring.h"
#include "../iplatformtimer.h"
#include "cfontmac.h"
#include "cgbitmap.h"
#include "quartzgraphicspath.h"
#include "coregraphicsdevicecontext.h"
#include "cocoa/nsviewframe.h"
#include "ios/uiviewframe.h"
#include "macclipboard.h"
#include "mactaskexecutor.h"
#include "macfactory.h"
#include "macfileselector.h"
#include "macglobals.h"
#include "macstring.h"
#include "mactimer.h"
#include <list>
#include <mach/mach_time.h>
#include <memory>
//-----------------------------------------------------------------------------
namespace VSTGUI {
//-----------------------------------------------------------------------------
struct MacFactory::Impl
{
struct mach_timebase_info timebaseInfo;
CFBundleRef bundle {nullptr};
bool useAsynchronousLayerDrawing {true};
bool visualizeRedrawAreas {false};
CoreGraphicsDeviceFactory graphicsDeviceFactory;
PlatformTaskExecutorPtr taskExecutor;
};
//-----------------------------------------------------------------------------
MacFactory::MacFactory (CFBundleRef bundle)
{
impl = std::unique_ptr<Impl> (new Impl);
impl->bundle = bundle;
mach_timebase_info (&impl->timebaseInfo);
impl->taskExecutor = std::make_unique<MacTaskExecutor> ();
}
MacFactory::~MacFactory () noexcept = default;
//------------------------------------------------------------------------
void MacFactory::finalize () noexcept { impl->taskExecutor->waitAllTasksExecuted (); }
//-----------------------------------------------------------------------------
CFBundleRef MacFactory::getBundle () const noexcept
{
return impl->bundle;
}
//-----------------------------------------------------------------------------
void MacFactory::setUseAsynchronousLayerDrawing (bool state) const noexcept
{
impl->useAsynchronousLayerDrawing = state;
}
//-----------------------------------------------------------------------------
bool MacFactory::getUseAsynchronousLayerDrawing () const noexcept
{
return impl->useAsynchronousLayerDrawing;
}
//-----------------------------------------------------------------------------
void MacFactory::enableVisualizeRedrawAreas (bool state) const noexcept
{
impl->visualizeRedrawAreas = state;
}
//-----------------------------------------------------------------------------
bool MacFactory::enableVisualizeRedrawAreas () const noexcept
{
return impl->visualizeRedrawAreas;
}
//-----------------------------------------------------------------------------
uint64_t MacFactory::getTicks () const noexcept
{
uint64_t absTime = mach_absolute_time ();
auto d = ((absTime * impl->timebaseInfo.numer) / impl->timebaseInfo.denom) / 1000000;
return d;
}
//-----------------------------------------------------------------------------
PlatformFramePtr MacFactory::createFrame (IPlatformFrameCallback* frame, const CRect& size,
void* parent, PlatformType parentType,
IPlatformFrameConfig* config) const noexcept
{
#if TARGET_OS_IPHONE
return makeOwned<UIViewFrame> (frame, size, (__bridge UIView*)parent);
#else
return makeOwned<NSViewFrame> (frame, size, reinterpret_cast<NSView*> (parent), config);
#endif
}
//-----------------------------------------------------------------------------
PlatformFontPtr MacFactory::createFont (const UTF8String& name, const CCoord& size,
const int32_t& style) const noexcept
{
auto font = makeOwned<CoreTextFont> (name, size, style);
if (font->getFontRef ())
return std::move (font);
return nullptr;
}
//-----------------------------------------------------------------------------
bool MacFactory::getAllFontFamilies (const FontFamilyCallback& callback) const noexcept
{
return CoreTextFont::getAllFontFamilies (callback);
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr MacFactory::createBitmap (const CPoint& size) const noexcept
{
return CGBitmap::create (&const_cast<CPoint&> (size));
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr MacFactory::createBitmap (const CResourceDescription& desc) const noexcept
{
if (auto bitmap = makeOwned<CGBitmap> ())
{
if (bitmap->load (desc))
return bitmap;
}
return nullptr;
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr MacFactory::createBitmapFromPath (UTF8StringPtr absolutePath) const noexcept
{
return CGBitmap::createFromPath (absolutePath);
}
//-----------------------------------------------------------------------------
PlatformBitmapPtr MacFactory::createBitmapFromMemory (const void* ptr,
uint32_t memSize) const noexcept
{
return CGBitmap::createFromMemory (ptr, memSize);
}
//-----------------------------------------------------------------------------
PNGBitmapBuffer
MacFactory::createBitmapMemoryPNGRepresentation (const PlatformBitmapPtr& bitmap) const noexcept
{
return CGBitmap::createMemoryPNGRepresentation (bitmap);
}
//-----------------------------------------------------------------------------
PlatformResourceInputStreamPtr
MacFactory::createResourceInputStream (const CResourceDescription& desc) const noexcept
{
if (desc.type == CResourceDescription::kIntegerType)
return nullptr;
if (auto bundle = getBundleRef ())
{
PlatformResourceInputStreamPtr result;
CFStringRef cfStr = CFStringCreateWithCString (nullptr, desc.u.name, kCFStringEncodingUTF8);
if (cfStr)
{
CFURLRef url = CFBundleCopyResourceURL (bundle, cfStr, nullptr, nullptr);
if (url)
{
char filePath[PATH_MAX];
if (CFURLGetFileSystemRepresentation (url, true, (UInt8*)filePath, PATH_MAX))
{
result = FileResourceInputStream::create (filePath);
}
CFRelease (url);
}
CFRelease (cfStr);
}
return result;
}
return nullptr;
}
//-----------------------------------------------------------------------------
PlatformStringPtr MacFactory::createString (UTF8StringPtr utf8String) const noexcept
{
return makeOwned<MacString> (utf8String);
}
//-----------------------------------------------------------------------------
PlatformTimerPtr MacFactory::createTimer (IPlatformTimerCallback* callback) const noexcept
{
return makeOwned<MacTimer> (callback);
}
//------------------------------------------------------------------------
bool MacFactory::setClipboard (const DataPackagePtr& data) const noexcept
{
#if TARGET_OS_IPHONE
return false;
#else
MacClipboard::setClipboard (data);
return true;
#endif
}
//------------------------------------------------------------------------
auto MacFactory::getClipboard () const noexcept -> DataPackagePtr
{
#if TARGET_OS_IPHONE
return nullptr;
#else
return MacClipboard::createClipboardDataPackage ();
#endif
}
//-----------------------------------------------------------------------------
PlatformGradientPtr MacFactory::createGradient () const noexcept
{
return std::make_unique<QuartzGradient> ();
}
//-----------------------------------------------------------------------------
PlatformFileSelectorPtr MacFactory::createFileSelector (PlatformFileSelectorStyle style,
IPlatformFrame* frame) const noexcept
{
#if !TARGET_OS_IPHONE
auto nsViewFrame = dynamic_cast<NSViewFrame*> (frame);
return createCocoaFileSelector (style, nsViewFrame);
#endif
return nullptr;
}
//-----------------------------------------------------------------------------
const IPlatformGraphicsDeviceFactory& MacFactory::getGraphicsDeviceFactory () const noexcept
{
return impl->graphicsDeviceFactory;
}
//------------------------------------------------------------------------
const IPlatformTaskExecutor& MacFactory::getTaskExecutor () const noexcept
{
return *impl->taskExecutor;
}
//------------------------------------------------------------------------
bool MacFactory::replaceTaskExecutor (const ReplaceTaskExecFunc& replaceFunc) const noexcept
{
if (!replaceFunc)
return false;
impl->taskExecutor = replaceFunc (std::move (impl->taskExecutor));
return true;
}
//-----------------------------------------------------------------------------
const LinuxFactory* MacFactory::asLinuxFactory () const noexcept
{
return nullptr;
}
//-----------------------------------------------------------------------------
const MacFactory* MacFactory::asMacFactory () const noexcept
{
return this;
}
//-----------------------------------------------------------------------------
const Win32Factory* MacFactory::asWin32Factory () const noexcept
{
return nullptr;
}
//-----------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,21 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#if !TARGET_OS_IPHONE
#include "../iplatformfileselector.h"
#include "cocoa/nsviewframe.h"
namespace VSTGUI {
//-----------------------------------------------------------------------------
PlatformFileSelectorPtr createCocoaFileSelector (PlatformFileSelectorStyle style,
NSViewFrame* frame);
} // VSTGUI
#endif
@@ -0,0 +1,298 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
/// @cond ignore
#import "../../cstring.h"
#import "cocoa/cocoahelpers.h"
#import "macfileselector.h"
#import "macstring.h"
#pragma clang diagnostic push
#if defined(VSTGUI_USE_OBJC_UTTYPE)
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#else
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
namespace VSTGUI {
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class CocoaFileSelector
: public IPlatformFileSelector
, public std::enable_shared_from_this<CocoaFileSelector>
{
public:
CocoaFileSelector (PlatformFileSelectorStyle style, NSViewFrame* frame);
~CocoaFileSelector () override = default;
bool run (const PlatformFileSelectorConfig& config) override;
bool cancel () override;
void openPanelDidEnd (NSSavePanel* panel, NSInteger resultCode);
protected:
static void initClass ();
void setupInitalDir (const PlatformFileSelectorConfig& config);
PlatformFileSelectorStyle style;
NSViewFrame* frame {nullptr};
NSSavePanel* savePanel {nullptr};
PlatformFileSelectorConfig::CallbackFunc callback;
};
//-----------------------------------------------------------------------------
PlatformFileSelectorPtr createCocoaFileSelector (PlatformFileSelectorStyle style,
NSViewFrame* frame)
{
return std::make_shared<CocoaFileSelector> (style, frame);
}
//-----------------------------------------------------------------------------
void CocoaFileSelector::initClass ()
{
}
//-----------------------------------------------------------------------------
CocoaFileSelector::CocoaFileSelector (PlatformFileSelectorStyle style, NSViewFrame* frame)
: style (style), frame (frame)
{
initClass ();
}
//-----------------------------------------------------------------------------
void CocoaFileSelector::openPanelDidEnd (NSSavePanel* panel, NSInteger res)
{
std::vector<UTF8String> result;
if (res == NSModalResponseOK)
{
if (style == PlatformFileSelectorStyle::SelectSaveFile)
{
NSURL* url = [panel URL];
const char* utf8Path = url ? [[url path] UTF8String] : nullptr;
if (utf8Path)
{
result.emplace_back (utf8Path);
}
}
else
{
NSOpenPanel* openPanel = (NSOpenPanel*)panel;
NSArray* urls = [openPanel URLs];
for (NSUInteger i = 0; i < [urls count]; i++)
{
NSURL* url = [urls objectAtIndex:i];
if (url == nullptr || [url path] == nullptr)
continue;
const char* utf8Path = [[url path] UTF8String];
if (utf8Path)
{
result.emplace_back (utf8Path);
}
}
}
}
if (callback)
callback (std::move (result));
}
//-----------------------------------------------------------------------------
bool CocoaFileSelector::cancel ()
{
if (savePanel)
{
[savePanel cancel:nil];
return true;
}
return false;
}
//-----------------------------------------------------------------------------
bool CocoaFileSelector::run (const PlatformFileSelectorConfig& config)
{
NSWindow* parentWindow = nil;
if (!hasBit (config.flags, PlatformFileSelectorFlags::RunModal) && frame)
parentWindow = [(frame->getNSView ()) window];
callback = config.doneCallback;
NSOpenPanel* openPanel = nil;
NSMutableArray* typesArray = nil;
if (config.extensions.empty () == false)
{
typesArray = [[[NSMutableArray alloc] init] autorelease];
for (auto& ext : config.extensions)
{
#ifdef VSTGUI_USE_OBJC_UTTYPE
UTType* uti = nullptr;
if (ext.uti.empty () == false)
uti = [UTType typeWithIdentifier:fromUTF8String<NSString*> (ext.uti)];
if (uti == nullptr && ext.mimeType.empty() == false)
uti = [UTType typeWithMIMEType:fromUTF8String<NSString*> (ext.mimeType)];
if (uti == nullptr && ext.extension.empty () == false)
uti = [UTType typeWithFilenameExtension:fromUTF8String<NSString*> (ext.extension)];
if (uti)
[typesArray addObject:uti];
#else
NSString* uti = nullptr;
if (ext.uti.empty () == false)
uti = [fromUTF8String<NSString*> (ext.uti) retain];
if (uti == nullptr && ext.mimeType.empty () == false)
uti = (NSString*)UTTypeCreatePreferredIdentifierForTag (
kUTTagClassMIMEType, fromUTF8String<CFStringRef> (ext.mimeType), kUTTypeData);
if (uti == nullptr && ext.macType)
{
NSString* osType =
(NSString*)UTCreateStringForOSType (static_cast<OSType> (ext.macType));
if (osType)
{
uti = (NSString*)UTTypeCreatePreferredIdentifierForTag (
kUTTagClassOSType, (CFStringRef)osType, kUTTypeData);
[osType release];
}
}
if (uti == nullptr && ext.extension.empty () == false)
uti = [fromUTF8String<NSString*> (ext.extension) retain];
if (uti)
{
[typesArray addObject:uti];
[uti release];
}
#endif
}
}
if (style == PlatformFileSelectorStyle::SelectSaveFile)
{
savePanel = [NSSavePanel savePanel];
if (typesArray)
{
#ifdef VSTGUI_USE_OBJC_UTTYPE
[savePanel setAllowedContentTypes:typesArray];
#else
[savePanel setAllowedFileTypes:typesArray];
#endif
}
}
else
{
savePanel = openPanel = [NSOpenPanel openPanel];
if (style == PlatformFileSelectorStyle::SelectFile)
{
bool allowMultiFileSelection =
hasBit (config.flags, PlatformFileSelectorFlags::MultiFileSelection);
[openPanel setAllowsMultipleSelection:allowMultiFileSelection ? YES : NO];
}
else
{
[openPanel setCanChooseDirectories:YES];
}
}
if (!config.title.empty () && savePanel)
{
#if 0 // Apple broke this again with macOS 12. Disable this now and always use the message to
// display the title.
if (@available (macOS 11, *))
{
if (parentWindow)
[savePanel setMessage:fromUTF8String<NSString*> (config.title)];
else
[savePanel setTitle:fromUTF8String<NSString*> (config.title)];
}
else
#endif
if (@available (macOS 10.11, *))
{
[savePanel setMessage:fromUTF8String<NSString*> (config.title)];
}
else
{
[savePanel setTitle:fromUTF8String<NSString*> (config.title)];
}
}
if (openPanel)
{
#ifdef VSTGUI_USE_OBJC_UTTYPE
openPanel.allowedContentTypes = typesArray;
#else
openPanel.allowedFileTypes = typesArray;
#endif
if (parentWindow)
{
setupInitalDir (config);
auto This = shared_from_this ();
[openPanel beginSheetModalForWindow:parentWindow
completionHandler:^(NSInteger result) {
This->openPanelDidEnd (openPanel, result);
}];
}
else
{
setupInitalDir (config);
NSInteger res = [openPanel runModal];
openPanelDidEnd (openPanel, res);
return res == NSModalResponseOK;
}
}
else if (savePanel)
{
#ifdef VSTGUI_USE_OBJC_UTTYPE
savePanel.allowedContentTypes = typesArray;
#else
savePanel.allowedFileTypes = typesArray;
#endif
if (parentWindow)
{
setupInitalDir (config);
auto This = shared_from_this ();
[savePanel beginSheetModalForWindow:parentWindow
completionHandler:^(NSInteger result) {
This->openPanelDidEnd (savePanel, result);
}];
}
else
{
setupInitalDir (config);
NSInteger res = [savePanel runModal];
openPanelDidEnd (savePanel, res);
return res == NSModalResponseOK;
}
}
return true;
}
//-----------------------------------------------------------------------------
void CocoaFileSelector::setupInitalDir (const PlatformFileSelectorConfig& config)
{
if (!config.initialPath.empty ())
{
NSURL* dirURL = [NSURL fileURLWithPath:fromUTF8String<NSString*> (config.initialPath)];
NSNumber* isDir;
if ([dirURL getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil])
{
if ([isDir boolValue] == NO)
{
savePanel.nameFieldStringValue =
[[dirURL.path lastPathComponent] stringByDeletingPathExtension];
dirURL = [NSURL fileURLWithPath:[dirURL.path stringByDeletingLastPathComponent]];
}
savePanel.directoryURL = dirURL;
}
}
if (!config.defaultSaveName.empty ())
{
savePanel.nameFieldStringValue = fromUTF8String<NSString*> (config.defaultSaveName);
}
}
} // VSTGUI
#pragma clang diagnostic pop
/// @endcond
@@ -0,0 +1,131 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "macglobals.h"
#if MAC
#include "../../cframe.h"
#include "../../ccolor.h"
#include "macfactory.h"
#include "../iplatformframe.h"
#include "../common/fileresourceinputstream.h"
#include <unordered_map>
#include <mutex>
namespace VSTGUI {
//-----------------------------------------------------------------------------
class CGColorMap
{
public:
struct ColorHash
{
size_t operator() (const CColor& c) const
{
return static_cast<size_t> (c.red | (c.green << 8) | (c.blue << 16) | (c.alpha << 24));
}
};
using Map = std::unordered_map<CColor, CGColorRef, ColorHash>;
static CGColorMap& instance ()
{
static CGColorMap gInstance;
return gInstance;
}
~CGColorMap () noexcept
{
std::for_each (map.begin (), map.end (), [] (auto& el) { CFRelease (el.second); });
}
CGColorRef getColor (const CColor& color)
{
mutex.lock ();
auto it = map.find (color);
if (it != map.end ())
{
auto result = it->second;
mutex.unlock ();
return result;
}
const CGFloat components[] = {color.normRed<CGFloat> (), color.normGreen<CGFloat> (),
color.normBlue<CGFloat> (), color.normAlpha<CGFloat> ()};
auto result = CGColorCreate (GetCGColorSpace (), components);
map.emplace (color, result);
mutex.unlock ();
return result;
}
Map map;
std::mutex mutex;
};
//-----------------------------------------------------------------------------
CGColorRef getCGColor (const CColor& color) { return CGColorMap::instance ().getColor (color); }
//-----------------------------------------------------------------------------
class GenericMacColorSpace
{
public:
GenericMacColorSpace ()
{
colorspace = CreateMainDisplayColorSpace ();
}
~GenericMacColorSpace ()
{
CGColorSpaceRelease (colorspace);
}
static GenericMacColorSpace& instance ()
{
static GenericMacColorSpace gInstance;
return gInstance;
}
//-----------------------------------------------------------------------------
static CGColorSpaceRef CreateMainDisplayColorSpace ()
{
#if TARGET_OS_IPHONE
return CGColorSpaceCreateDeviceRGB ();
#else
ColorSyncProfileRef csProfileRef = ColorSyncProfileCreateWithDisplayID (CGMainDisplayID ());
if (csProfileRef)
{
CGColorSpaceRef colorSpace = {};
#if defined(MAC_OS_VERSION_12_0) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0)
if (__builtin_available (macOS 12.0, *))
{
colorSpace = CGColorSpaceCreateWithColorSyncProfile (csProfileRef, nullptr);
}
#if (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_12_0)
else
{
colorSpace = CGColorSpaceCreateWithPlatformColorSpace (csProfileRef);
}
#endif
#else
colorSpace = CGColorSpaceCreateWithPlatformColorSpace (csProfileRef);
#endif
CFRelease (csProfileRef);
return colorSpace;
}
return nullptr;
#endif
}
CGColorSpaceRef colorspace;
};
//-----------------------------------------------------------------------------
CGColorSpaceRef GetCGColorSpace ()
{
return GenericMacColorSpace::instance ().colorspace;
}
} // VSTGUI
#endif // MAC

Some files were not shown because too many files have changed in this diff Show More