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,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 "../lib/cbitmapfilter.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace BitmapFilter {
//------------------------------------------------------------------------
class CIBoxBlurFilter : public BitmapFilter::FilterBase
{
public:
CIBoxBlurFilter ();
bool run (bool replace) override;
static IFilter* CreateFunction (IdStringPtr _name) { return new CIBoxBlurFilter (); }
};
//------------------------------------------------------------------------
} // BitmapFilter
} // 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
#import "ciboxblurfilter.h"
#import "../lib/platform/mac/cgbitmap.h"
#import "../lib/cbitmap.h"
#import <QuartzCore/QuartzCore.h>
namespace VSTGUI {
namespace BitmapFilter {
//------------------------------------------------------------------------
__attribute__((__constructor__)) static void registerFilter ()
{
Factory::getInstance ().registerFilter (Standard::kBoxBlur, CIBoxBlurFilter::CreateFunction);
}
//------------------------------------------------------------------------
CIBoxBlurFilter::CIBoxBlurFilter () : FilterBase ("A Box Blur Filter using CoreImage")
{
registerProperty (Standard::Property::kInputBitmap,
BitmapFilter::Property (BitmapFilter::Property::kObject));
registerProperty (Standard::Property::kRadius,
BitmapFilter::Property (static_cast<int32_t> (2)));
}
//------------------------------------------------------------------------
bool CIBoxBlurFilter::run (bool replace)
{
CBitmap* inputBitmap = getInputBitmap ();
int32_t radius = static_cast<int32_t> (
static_cast<double> (getProperty (Standard::Property::kRadius).getInteger ()) *
inputBitmap->getPlatformBitmap ()->getScaleFactor ());
if (inputBitmap == nullptr)
return false;
CGBitmap* cgBitmap = dynamic_cast<CGBitmap*> (inputBitmap->getPlatformBitmap ());
if (cgBitmap == nullptr)
return false;
CIImage* inputImage = [[[CIImage alloc] initWithCGImage:cgBitmap->getCGImage ()] autorelease];
if (inputImage == nil)
return false;
CIFilter* filter = [CIFilter filterWithName:@"CIBoxBlur"];
NSMutableDictionary* values = [[NSMutableDictionary new] autorelease];
[values setObject:@(radius) forKey:@"inputRadius"];
[values setObject:inputImage forKey:@"inputImage"];
[filter setValuesForKeysWithDictionary:values];
CIImage* outputImage = [filter valueForKey:@"outputImage"];
if (outputImage == nil)
return false;
SharedPointer<CGBitmap> outputBitmap = owned (new CGBitmap (cgBitmap->getSize ()));
CGContextRef cgContext = outputBitmap->createCGContext ();
if (cgContext == nullptr)
return false;
CGContextScaleCTM (cgContext, 1, -1);
CIContext* context = [CIContext contextWithCGContext:cgContext options:nil];
if (context == nil)
return false;
[context drawImage:outputImage
atPoint:CGPointMake (0, -cgBitmap->getSize ().y)
fromRect:CGRectMake (0, 0, cgBitmap->getSize ().x, cgBitmap->getSize ().y)];
CFRelease (cgContext);
outputBitmap->setScaleFactor (cgBitmap->getScaleFactor ());
if (replace)
{
inputBitmap->setPlatformBitmap (outputBitmap);
return true;
}
return registerProperty (Standard::Property::kOutputBitmap,
BitmapFilter::Property (owned (new CBitmap (outputBitmap))));
}
//------------------------------------------------------------------------
} // BitmapFilter
} // VSTGUI
@@ -0,0 +1,54 @@
// 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 "../lib/iexternalview.h"
#include <functional>
#include <memory>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
class DatePicker : public ViewAdapter
{
public:
DatePicker ();
~DatePicker () noexcept;
struct Date
{
int32_t day {0};
int32_t month {0};
int32_t year {0};
};
void setDate (Date date);
using ChangeCallback = std::function<void (Date)>;
void setChangeCallback (const ChangeCallback& callback);
private:
bool platformViewTypeSupported (PlatformViewType type) override;
bool attach (void* parent, PlatformViewType parentViewType) override;
bool remove () override;
void setViewSize (IntRect frame, IntRect visible) override;
void setContentScaleFactor (double scaleFactor) override;
void setMouseEnabled (bool state) override;
void takeFocus () override;
void looseFocus () override;
void setTookFocusCallback (const TookFocusCallback& callback) override;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,199 @@
// 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 "datepicker.h"
#import "externalview_nsview.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
struct DatePickerDelegate : RuntimeObjCClass<DatePickerDelegate>
{
using DoneCallback = std::function<void ()>;
using ValidateCallback = std::function<void (NSDate**, NSTimeInterval*)>;
static constexpr const auto DoneCallbackVarName = "DoneCallback";
static constexpr const auto ValidateCallbackVarName = "ValidateCallback";
static id allocAndInit (DoneCallback&& doneCallback, ValidateCallback&& callback)
{
id obj = Base::alloc ();
initWithCallbacks (obj, std::move (doneCallback), std::move (callback));
return obj;
}
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("DatePickerDelegate", [NSObject class])
.addProtocol ("NSDatePickerCellDelegate")
.addMethod (@selector (datePickerCell:validateProposedDateValue:timeInterval:),
validate)
.addMethod (@selector (complete:), complete)
.addIvar<ValidateCallback> (ValidateCallbackVarName)
.addIvar<DoneCallback> (DoneCallbackVarName)
.finalize ();
}
static id initWithCallbacks (id self, DoneCallback&& doneCallback, ValidateCallback&& callback)
{
if ((self = makeInstance (self).callSuper<id (), id> (@selector (init))))
{
auto instance = makeInstance (self);
if (auto var = instance.getVariable<DoneCallback> (DoneCallbackVarName))
var->set (doneCallback);
if (auto var = instance.getVariable<ValidateCallback> (ValidateCallbackVarName))
var->set (callback);
}
return self;
}
static void complete (id self, SEL cmd, id sender)
{
if (auto var = makeInstance (self).getVariable<DoneCallback> (DoneCallbackVarName))
{
const auto& callback = var->get ();
if (callback)
callback ();
}
}
static void validate (id self, SEL cmd, NSDatePickerCell* datePickerCell,
NSDate* _Nonnull* _Nonnull proposedDateValue,
NSTimeInterval* _Nullable proposedTimeInterval)
{
if (auto var = makeInstance (self).getVariable<ValidateCallback> (ValidateCallbackVarName))
{
const auto& callback = var->get ();
if (callback)
callback (proposedDateValue, proposedTimeInterval);
}
}
};
//------------------------------------------------------------------------
struct DatePicker::Impl : ExternalNSViewBase<NSDatePicker>
{
using Base::Base;
id delegate {nil};
ChangeCallback changeCallback;
#if !__has_feature(objc_arc)
~Impl () noexcept
{
if (delegate)
[delegate release];
}
#endif
};
//------------------------------------------------------------------------
DatePicker::DatePicker ()
{
impl = std::make_unique<Impl> ([[NSDatePicker alloc] initWithFrame: {0., 0., 10., 10.}]);
impl->view.datePickerStyle = NSDatePickerStyleTextField;
impl->view.datePickerMode = NSDatePickerModeSingle;
impl->view.datePickerElements = NSDatePickerElementFlagYearMonthDay;
if (@available (macOS 10.15.4, *))
impl->view.presentsCalendarOverlay = YES;
impl->view.dateValue = [NSDate date];
impl->view.calendar = [NSCalendar currentCalendar];
[impl->container addSubview:impl->view];
impl->delegate = DatePickerDelegate::allocAndInit (
[impl = impl.get ()] () {
if (impl->changeCallback)
{
auto dateValue = impl->view.dateValue;
auto calendar = impl->view.calendar;
auto components = [calendar
components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay
fromDate:dateValue];
Date date;
date.day = static_cast<int32_t> (components.day);
date.month = static_cast<int32_t> (components.month);
date.year = static_cast<int32_t> (components.year);
impl->changeCallback (date);
}
},
[] (NSDate** date, NSTimeInterval* time) {
// TODO: add validation mechanism
});
impl->view.delegate = impl->delegate;
impl->view.target = impl->delegate;
impl->view.action = @selector (complete:);
}
//------------------------------------------------------------------------
DatePicker::~DatePicker () noexcept {}
//------------------------------------------------------------------------
void DatePicker::setDate (Date date)
{
auto calendar = impl->view.calendar;
auto dateComponents = [NSDateComponents new];
dateComponents.calendar = calendar;
dateComponents.day = date.day;
dateComponents.month = date.month;
dateComponents.year = date.year;
impl->view.dateValue = [calendar dateFromComponents:dateComponents];
#if !__has_feature(objc_arc)
[dateComponents release];
#endif
}
//------------------------------------------------------------------------
void DatePicker::setChangeCallback (const ChangeCallback& callback)
{
impl->changeCallback = callback;
}
//------------------------------------------------------------------------
bool DatePicker::platformViewTypeSupported (PlatformViewType type)
{
return impl->platformViewTypeSupported (type);
}
//------------------------------------------------------------------------
bool DatePicker::attach (void* parent, PlatformViewType parentViewType)
{
return impl->attach (parent, parentViewType);
}
//------------------------------------------------------------------------
bool DatePicker::remove () { return impl->remove (); }
//------------------------------------------------------------------------
void DatePicker::setViewSize (IntRect frame, IntRect visible)
{
impl->setViewSize (frame, visible);
}
//------------------------------------------------------------------------
void DatePicker::setContentScaleFactor (double scaleFactor)
{
impl->setContentScaleFactor (scaleFactor);
}
//------------------------------------------------------------------------
void DatePicker::setMouseEnabled (bool state) { impl->setMouseEnabled (state); }
//------------------------------------------------------------------------
void DatePicker::takeFocus () { impl->takeFocus (); }
//------------------------------------------------------------------------
void DatePicker::looseFocus () { impl->looseFocus (); }
//------------------------------------------------------------------------
void DatePicker::setTookFocusCallback (const TookFocusCallback& callback)
{
impl->setTookFocusCallback (callback);
}
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,132 @@
#include "datepicker.h"
#include "externalview_hwnd.h"
#include "vstgui/lib/platform/win32/win32factory.h"
#include <CommCtrl.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
struct DatePicker::Impl : ExternalHWNDBase
{
using Base::Base;
~Impl () noexcept
{
if (font)
DeleteObject (font);
}
ChangeCallback changeCallback;
HFONT font {nullptr};
};
//------------------------------------------------------------------------
DatePicker::DatePicker ()
{
auto hInstance = getPlatformFactory ().asWin32Factory ()->getInstance ();
impl = std::make_unique<Impl> (hInstance);
impl->child = CreateWindowExW (0, DATETIMEPICK_CLASS, TEXT ("DateTime"),
WS_BORDER | WS_CHILD | WS_VISIBLE | DTS_SHORTDATEFORMAT, 0, 0,
80, 20, impl->container.getHWND (), NULL, hInstance, NULL);
impl->container.setWindowProc ([this] (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message)
{
case WM_NOTIFY:
{
LPNMHDR hdr = reinterpret_cast<LPNMHDR> (lParam);
switch (hdr->code)
{
case DTN_DATETIMECHANGE:
{
LPNMDATETIMECHANGE lpChange = reinterpret_cast<LPNMDATETIMECHANGE> (lParam);
if (impl->changeCallback)
{
Date date;
date.day = lpChange->st.wDay;
date.month = lpChange->st.wMonth;
date.year = lpChange->st.wYear;
impl->changeCallback (date);
}
break;
}
}
break;
}
}
return DefWindowProc (hwnd, message, wParam, lParam);
});
}
//------------------------------------------------------------------------
DatePicker::~DatePicker () noexcept {}
//------------------------------------------------------------------------
void DatePicker::setDate (Date date)
{
SYSTEMTIME st = {};
st.wDay = date.day;
st.wMonth = date.month;
st.wYear = date.year;
DateTime_SetSystemtime (impl->child, GDT_VALID, &st);
}
//------------------------------------------------------------------------
void DatePicker::setChangeCallback (const ChangeCallback& callback)
{
impl->changeCallback = callback;
}
//------------------------------------------------------------------------
bool DatePicker::platformViewTypeSupported (PlatformViewType type)
{
return impl->platformViewTypeSupported (type);
}
//------------------------------------------------------------------------
bool DatePicker::attach (void* parent, PlatformViewType parentViewType)
{
return impl->attach (parent, parentViewType);
}
//------------------------------------------------------------------------
bool DatePicker::remove () { return impl->remove (); }
//------------------------------------------------------------------------
void DatePicker::setViewSize (IntRect frame, IntRect visible)
{
impl->setViewSize (frame, visible);
}
//------------------------------------------------------------------------
void DatePicker::setContentScaleFactor (double scaleFactor)
{
if (impl->font)
DeleteObject (impl->font);
auto logFont = NonClientMetrics::get ().lfCaptionFont;
logFont.lfHeight = static_cast<LONG> (std::round (logFont.lfHeight * scaleFactor));
impl->font = CreateFontIndirect (&logFont);
if (impl->font)
SendMessage (impl->child, WM_SETFONT, (WPARAM)impl->font, 0);
}
//------------------------------------------------------------------------
void DatePicker::setMouseEnabled (bool state) { impl->setMouseEnabled (state); }
//------------------------------------------------------------------------
void DatePicker::takeFocus () { impl->takeFocus (); }
//------------------------------------------------------------------------
void DatePicker::looseFocus () { impl->looseFocus (); }
void DatePicker::setTookFocusCallback (const TookFocusCallback& callback)
{
impl->setTookFocusCallback (callback);
}
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
+54
View File
@@ -0,0 +1,54 @@
// 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 "../lib/iexternalview.h"
#include "../lib/cstring.h"
#include <memory>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
class Button : public ControlViewAdapter
{
public:
enum class Type
{
Checkbox,
Push,
Radio,
OnOff
};
Button (Type type, const UTF8String& title);
~Button () noexcept;
private:
bool platformViewTypeSupported (PlatformViewType type) override;
bool attach (void* parent, PlatformViewType parentViewType) override;
bool remove () override;
void setViewSize (IntRect frame, IntRect visible) override;
void setContentScaleFactor (double scaleFactor) override;
void setMouseEnabled (bool state) override;
void takeFocus () override;
void looseFocus () override;
void setTookFocusCallback (const TookFocusCallback& callback) override;
bool setValue (double value) override;
bool setEditCallbacks (const EditCallbacks& callbacks) override;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,232 @@
// 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 "evbutton.h"
#import "externalview_nsview.h"
#import "../lib/platform/mac/macstring.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
struct ButtonDelegate : RuntimeObjCClass<ButtonDelegate>
{
using ActionCallback = std::function<void ()>;
static constexpr const auto ActionCallbackVarName = "ActionCallback";
static id allocAndInit (ActionCallback&& actionCallback)
{
id obj = Base::alloc ();
initWithCallbacks (obj, std::move (actionCallback));
return obj;
}
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("ButtonDelegate", [NSObject class])
.addMethod (@selector (onAction:), onAction)
.addIvar<ActionCallback> (ActionCallbackVarName)
.finalize ();
}
static id initWithCallbacks (id self, ActionCallback&& actionCallback)
{
if ((self = makeInstance (self).callSuper<id (), id> (@selector (init))))
{
auto instance = makeInstance (self);
if (auto var = instance.getVariable<ActionCallback> (ActionCallbackVarName))
var->set (actionCallback);
}
return self;
}
static void onAction (id self, SEL cmd, id sender)
{
if (auto var = makeInstance (self).getVariable<ActionCallback> (ActionCallbackVarName))
{
const auto& callback = var->get ();
if (callback)
callback ();
}
}
};
//------------------------------------------------------------------------
struct Button::Impl : ExternalNSViewBase<NSButton>,
IControlViewExtension
{
using Base::Base;
id delegate {nil};
EditCallbacks callbacks {};
#if !__has_feature(objc_arc)
~Impl () noexcept
{
if (delegate)
[delegate release];
}
#endif
bool setValue (double value) override
{
if (value < 0.5)
view.state = NSControlStateValueOff;
else if (value == 0.5)
view.state = NSControlStateValueMixed;
else
view.state = NSControlStateValueOn;
return true;
}
bool setEditCallbacks (const EditCallbacks& editCallbacks) override
{
callbacks = editCallbacks;
return true;
}
};
//------------------------------------------------------------------------
Button::Button (Type type, const UTF8String& inTitle)
{
NSString* title = fromUTF8String<NSString*> (inTitle);
ButtonDelegate::ActionCallback actionCallback = [this] () {
double value = 0.;
switch (impl->view.state)
{
case NSControlStateValueOn:
value = 1.;
break;
case NSControlStateValueOff:
value = 0.;
break;
case NSControlStateValueMixed:
value = 0.5;
break;
}
if (impl->callbacks.beginEdit)
impl->callbacks.beginEdit ();
if (impl->callbacks.performEdit)
impl->callbacks.performEdit (value);
if (impl->callbacks.endEdit)
impl->callbacks.endEdit ();
};
NSButton* button = {};
switch (type)
{
case Type::Checkbox:
{
button = [NSButton checkboxWithTitle:title target:nullptr action:nullptr];
break;
}
case Type::Push:
{
button = [NSButton buttonWithTitle:title target:nullptr action:nullptr];
[button setButtonType:NSButtonTypeMomentaryLight];
actionCallback = [this] () {
if (impl->callbacks.beginEdit)
impl->callbacks.beginEdit ();
if (impl->callbacks.performEdit)
impl->callbacks.performEdit (1.);
if (impl->callbacks.endEdit)
impl->callbacks.endEdit ();
if (impl->callbacks.beginEdit)
impl->callbacks.beginEdit ();
if (impl->callbacks.performEdit)
impl->callbacks.performEdit (0.);
if (impl->callbacks.endEdit)
impl->callbacks.endEdit ();
};
break;
}
case Type::OnOff:
{
button = [NSButton buttonWithTitle:title target:nullptr action:nullptr];
[button setButtonType:NSButtonTypePushOnPushOff];
break;
}
case Type::Radio:
{
button = [NSButton radioButtonWithTitle:title target:nullptr action:nullptr];
break;
}
}
[button sizeToFit];
impl = std::make_unique<Impl> (button);
impl->delegate = ButtonDelegate::allocAndInit (std::move (actionCallback));
impl->view.target = impl->delegate;
impl->view.action = @selector (onAction:);
[impl->container addSubview:impl->view];
[button retain];
}
//------------------------------------------------------------------------
Button::~Button () noexcept = default;
//------------------------------------------------------------------------
bool Button::platformViewTypeSupported (PlatformViewType type)
{
return impl->platformViewTypeSupported (type);
}
//------------------------------------------------------------------------
bool Button::attach (void* parent, PlatformViewType parentViewType)
{
return impl->attach (parent, parentViewType);
}
//------------------------------------------------------------------------
bool Button::remove () { return impl->remove (); }
//------------------------------------------------------------------------
void Button::setViewSize (IntRect frame, IntRect visible)
{
static constexpr const NSControlSize controlSizes[] = {NSControlSizeRegular, NSControlSizeSmall,
NSControlSizeMini};
for (auto i = 0; i < std::size (controlSizes); i++)
{
impl->view.controlSize = controlSizes[i];
auto size = [impl->view sizeThatFits:NSMakeSize (frame.size.width, frame.size.height)];
if (size.height <= frame.size.height)
break;
}
impl->setViewSize (frame, visible);
}
//------------------------------------------------------------------------
void Button::setContentScaleFactor (double scaleFactor)
{
impl->setContentScaleFactor (scaleFactor);
}
//------------------------------------------------------------------------
void Button::setMouseEnabled (bool state) { impl->setMouseEnabled (state); }
//------------------------------------------------------------------------
void Button::takeFocus () { impl->takeFocus (); }
//------------------------------------------------------------------------
void Button::looseFocus () { impl->looseFocus (); }
//------------------------------------------------------------------------
void Button::setTookFocusCallback (const TookFocusCallback& callback)
{
impl->setTookFocusCallback (callback);
}
//------------------------------------------------------------------------
bool Button::setValue (double value) { return impl->setValue (value); }
//------------------------------------------------------------------------
bool Button::setEditCallbacks (const EditCallbacks& callbacks)
{
return impl->setEditCallbacks (callbacks);
}
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,198 @@
// 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 "evbutton.h"
#include "externalview_hwnd.h"
#include "vstgui/lib/platform/win32/win32factory.h"
#include "vstgui/lib/platform/win32/winstring.h"
#include <windowsx.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
struct Button::Impl : ExternalHWNDBase,
IControlViewExtension
{
using ExternalHWNDBase::ExternalHWNDBase;
Type type {};
EditCallbacks callbacks {};
double value {0.};
bool setValue (double val) override
{
value = val;
auto state = Button_GetState (child);
switch (type)
{
case Type::Checkbox:
case Type::Radio:
{
Button_SetCheck (child, value > 0.5);
break;
}
{
break;
}
case Type::OnOff:
{
if (value == 0)
state = 0; //~BST_PUSHED;
else
state = BST_PUSHED;
Button_SetState (child, state);
break;
}
case Type::Push:
{
break;
}
}
return true;
}
bool setEditCallbacks (const EditCallbacks& editCallbacks) override
{
callbacks = editCallbacks;
return true;
}
void onButtonClick (double val)
{
if (callbacks.beginEdit)
callbacks.beginEdit ();
if (callbacks.performEdit)
callbacks.performEdit (val);
if (callbacks.endEdit)
callbacks.endEdit ();
}
};
//------------------------------------------------------------------------
Button::Button (Type type, const UTF8String& inTitle)
{
DWORD addStyle = 0;
switch (type)
{
case Type::Checkbox:
addStyle = BS_AUTOCHECKBOX;
break;
case Type::Radio:
addStyle = BS_RADIOBUTTON;
break;
case Type::OnOff:
addStyle = BS_PUSHBUTTON;
break;
case Type::Push:
addStyle = BS_PUSHBUTTON;
break;
}
auto winString = dynamic_cast<WinString*> (inTitle.getPlatformString ());
auto hInstance = getPlatformFactory ().asWin32Factory ()->getInstance ();
impl = std::make_unique<Impl> (hInstance);
impl->type = type;
impl->child = CreateWindowExW (WS_EX_COMPOSITED, TEXT ("BUTTON"),
winString ? winString->getWideString () : nullptr,
WS_CHILD | WS_VISIBLE | BS_TEXT | addStyle, 0, 0, 80, 20,
impl->container.getHWND (), NULL, hInstance, NULL);
impl->container.setWindowProc (
[this] (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) -> LRESULT {
switch (message)
{
case WM_COMMAND:
{
if (HIWORD (wParam) == BN_CLICKED)
{
switch (impl->type)
{
case Type::Checkbox:
{
impl->onButtonClick (impl->value == 0. ? 1 : 0.);
break;
}
case Type::Radio:
{
impl->onButtonClick (impl->value == 0. ? 1 : 0.);
break;
}
case Type::OnOff:
{
impl->onButtonClick (impl->value == 0. ? 1 : 0.);
break;
}
case Type::Push:
{
impl->onButtonClick (1.);
impl->onButtonClick (0.);
break;
}
}
return 0;
}
break;
}
case WM_ERASEBKGND:
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam);
});
}
//------------------------------------------------------------------------
Button::~Button () noexcept = default;
//------------------------------------------------------------------------
bool Button::platformViewTypeSupported (PlatformViewType type)
{
return impl->platformViewTypeSupported (type);
}
//------------------------------------------------------------------------
bool Button::attach (void* parent, PlatformViewType parentViewType)
{
return impl->attach (parent, parentViewType);
}
//------------------------------------------------------------------------
bool Button::remove () { return impl->remove (); }
//------------------------------------------------------------------------
void Button::setViewSize (IntRect frame, IntRect visible) { impl->setViewSize (frame, visible); }
//------------------------------------------------------------------------
void Button::setContentScaleFactor (double scaleFactor)
{
impl->setContentScaleFactor (scaleFactor);
}
//------------------------------------------------------------------------
void Button::setMouseEnabled (bool state) { impl->setMouseEnabled (state); }
//------------------------------------------------------------------------
void Button::takeFocus () { impl->takeFocus (); }
//------------------------------------------------------------------------
void Button::looseFocus () { impl->looseFocus (); }
//------------------------------------------------------------------------
void Button::setTookFocusCallback (const TookFocusCallback& callback)
{
impl->setTookFocusCallback (callback);
}
//------------------------------------------------------------------------
bool Button::setValue (double value) { return impl->setValue (value); }
//------------------------------------------------------------------------
bool Button::setEditCallbacks (const EditCallbacks& callbacks)
{
return impl->setEditCallbacks (callbacks);
}
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,481 @@
// 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 "externalview_hwnd.h"
#include <d3d12.h>
#include <dcomp.h>
#include <dxgi1_4.h>
#include <wrl.h>
#include <comdef.h>
#include <mutex>
#ifdef _MSC_VER
#pragma comment(lib, "dcomp.lib")
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#endif
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
//------------------------------------------------------------------------
struct Win32Exception : std::exception
{
explicit Win32Exception (HRESULT hr) : _hr (hr)
{
FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&_errorStr, 0,
NULL);
}
~Win32Exception () noexcept
{
if (_errorStr)
LocalFree ((HLOCAL)_errorStr);
}
const char* what () const noexcept override { return _errorStr; }
HRESULT hr () const noexcept { return _hr; }
private:
HRESULT _hr;
char* _errorStr {nullptr};
};
inline void ThrowIfFailed (HRESULT hr)
{
if (FAILED (hr))
{
throw Win32Exception (hr);
}
}
//------------------------------------------------------------------------
namespace ExternalView {
//------------------------------------------------------------------------
struct IDirect3D12View
{
virtual ~IDirect3D12View () noexcept = default;
virtual ID3D12CommandAllocator* getCommandAllocator () const = 0;
virtual IDXGISwapChain3* getSwapChain () const = 0;
virtual ID3D12Device* getDevice () const = 0;
virtual INT getFrameIndex () const = 0;
virtual void setFrameIndex (INT index) = 0;
virtual void render () = 0;
};
//------------------------------------------------------------------------
struct IDirect3D12Renderer
{
virtual ~IDirect3D12Renderer () noexcept = default;
virtual bool init (IDirect3D12View* view) = 0;
virtual void render (ID3D12CommandQueue* queue) = 0;
virtual void beforeSizeUpdate () = 0;
virtual void onSizeUpdate (IntSize newSize, double scaleFactor) = 0;
virtual void onAttach () = 0;
virtual void onRemove () = 0;
virtual uint32_t getFrameCount () const = 0;
};
//------------------------------------------------------------------------
using Direct3D12RendererPtr = std::shared_ptr<IDirect3D12Renderer>;
//------------------------------------------------------------------------
struct GPUFence
{
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
GPUFence () = default;
GPUFence (ID3D12Device* device, UINT64 initialValue = 0)
{
ThrowIfFailed (device->CreateFence (0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS (&m_fence)));
m_event = CreateEvent (nullptr, FALSE, FALSE, nullptr);
if (m_event == nullptr)
{
ThrowIfFailed (HRESULT_FROM_WIN32 (GetLastError ()));
}
m_value = initialValue;
}
~GPUFence () noexcept
{
if (m_event)
CloseHandle (m_event);
}
GPUFence& operator=(GPUFence&& o) noexcept
{
m_event = o.m_event;
m_fence = o.m_fence;
m_value = o.m_value;
o.m_event = nullptr;
o.m_fence.Reset ();
o.m_value = {};
return *this;
}
void wait (ID3D12CommandQueue* queue)
{
if (m_fence == nullptr)
return;
const auto value = m_value;
ThrowIfFailed (queue->Signal (m_fence.Get (), value));
m_value++;
if (m_fence->GetCompletedValue () < value)
{
ThrowIfFailed (m_fence->SetEventOnCompletion (value, m_event));
WaitForSingleObject (m_event, INFINITE);
}
}
HANDLE m_event {nullptr};
ComPtr<ID3D12Fence> m_fence;
UINT64 m_value {0};
};
//------------------------------------------------------------------------
struct Direct3D12View : public ExternalHWNDBase,
IDirect3D12View
{
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
Direct3D12View (HINSTANCE instance, const Direct3D12RendererPtr& renderer,
ComPtr<IDXGIFactory4> factory = nullptr, ComPtr<ID3D12Device> device = nullptr, ComPtr<ID3D12CommandQueue> commandQueue = nullptr)
: Base (instance), m_renderer (renderer), m_factory (factory), m_device (device), m_commandQueue (commandQueue)
{
vstgui_assert ((factory && device) || (!factory && !device), "Either both factory and device are provided or none of both!");
vstgui_assert (commandQueue ? device : true, "If a command queue is provided, the device must also be provided!");
}
static std::shared_ptr<Direct3D12View> make (HINSTANCE instance,
const Direct3D12RendererPtr& renderer,
ComPtr<IDXGIFactory4> factory = nullptr,
ComPtr<ID3D12Device> device = nullptr,
ComPtr<ID3D12CommandQueue> queue = nullptr)
{
return std::make_shared<Direct3D12View> (instance, renderer, factory, device, queue);
}
void render () override { doRender (); }
Direct3D12RendererPtr& getRenderer () { return m_renderer; }
const Direct3D12RendererPtr& getRenderer () const { return m_renderer; }
private:
ID3D12CommandAllocator* getCommandAllocator () const { return m_commandAllocator.Get (); }
IDXGISwapChain3* getSwapChain () const { return m_swapChain.Get (); }
ID3D12Device* getDevice () const { return m_device.Get (); }
INT getFrameIndex () const { return m_frameIndex; }
void setFrameIndex (INT index) { m_frameIndex = index; }
void doRender ()
{
if (mutex.try_lock ())
{
if (m_commandQueue)
{
HRESULT result = S_FALSE;
try
{
waitForPreviousFrame ();
m_renderer->render (m_commandQueue.Get ());
result = getSwapChain ()->Present (1, 0);
ThrowIfFailed (result);
}
catch (const Win32Exception& e)
{
try
{
freeResources ();
}
catch (...)
{
}
throw (e);
}
}
mutex.unlock ();
}
}
bool attach (void* parent, PlatformViewType parentViewType) override
{
if (Base::attach (parent, parentViewType))
{
try
{
if (m_renderer->init (this))
{
init ();
m_renderer->onAttach ();
}
}
catch (...)
{
auto reasonHR = m_device->GetDeviceRemovedReason ();
Win32Exception e (reasonHR);
freeResources ();
}
return true;
}
return false;
}
bool remove () override
{
Guard g (mutex);
waitForPreviousFrame ();
freeResources ();
return Base::remove ();
}
void setViewSize (IntRect frame, IntRect visible) override
{
Guard g (mutex);
Base::setViewSize (frame, visible);
m_visibleRect = visible;
updateSizes ();
}
void setContentScaleFactor (double factor) override
{
Guard g (mutex);
m_scaleFactor = factor;
updateSizes ();
}
void updateSizes ()
{
if (m_swapChain)
{
if (m_size.width == m_visibleRect.size.width &&
m_size.height == m_visibleRect.size.height)
return;
m_size = m_visibleRect.size;
waitForPreviousFrame ();
m_renderer->beforeSizeUpdate ();
ThrowIfFailed (m_dcompVisual->SetContent (nullptr));
ThrowIfFailed (m_swapChain->ResizeBuffers (
m_renderer->getFrameCount (), static_cast<UINT> (m_size.width),
static_cast<UINT> (m_size.height), DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_SWAP_EFFECT_FLIP_DISCARD));
ThrowIfFailed (m_dcompVisual->SetContent (m_swapChain.Get ()));
m_renderer->onSizeUpdate (m_size, m_scaleFactor);
ThrowIfFailed (m_dcompDevice->Commit ());
}
}
void freeResources ()
{
m_fence = {};
try
{
m_renderer->onRemove ();
}
catch (...)
{
}
m_commandAllocator.Reset ();
m_commandQueue.Reset ();
m_dcompDevice.Reset ();
m_dcompTarget.Reset ();
m_dcompVisual.Reset ();
m_swapChain.Reset ();
m_device.Reset ();
}
void init ()
{
#if defined(_DEBUG)
// Enable the D3D12 debug layer.
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED (D3D12GetDebugInterface (IID_PPV_ARGS (&debugController))))
{
debugController->EnableDebugLayer ();
}
}
#endif
if (!m_factory)
ThrowIfFailed (CreateDXGIFactory1 (IID_PPV_ARGS (&m_factory)));
if (m_device == nullptr)
{
ComPtr<IDXGIAdapter1> hardwareAdapter;
getHardwareAdapter (m_factory.Get (), &hardwareAdapter);
if (!hardwareAdapter)
{
throw;
}
ThrowIfFailed (D3D12CreateDevice (hardwareAdapter.Get (), D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS (&m_device)));
}
if (!m_commandQueue)
{
// Describe and create the command queue.
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
ThrowIfFailed (m_device->CreateCommandQueue (&queueDesc, IID_PPV_ARGS (&m_commandQueue)));
}
try
{
createSwapChain (m_factory.Get ());
setupDirectComposition ();
}
catch (...)
{
auto exc = std::current_exception ();
throw (exc);
}
ThrowIfFailed (
m_factory->MakeWindowAssociation (container.getHWND (), DXGI_MWA_NO_ALT_ENTER));
ThrowIfFailed (m_device->CreateCommandAllocator (D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS (&m_commandAllocator)));
m_fence = GPUFence (m_device.Get (), 1);
}
void createSwapChain (IDXGIFactory4* factory)
{
// Describe and create the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = m_renderer->getFrameCount ();
swapChainDesc.Width = static_cast<UINT> (m_size.width);
swapChainDesc.Height = static_cast<UINT> (m_size.height);
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
ComPtr<IDXGISwapChain1> swapChain;
ThrowIfFailed (factory->CreateSwapChainForComposition (
m_commandQueue.Get (), // Swap chain needs the queue so that it can force a flush on it.
&swapChainDesc, nullptr, &swapChain));
ThrowIfFailed (swapChain.As (&m_swapChain));
}
void setupDirectComposition ()
{
// Create the DirectComposition device
ThrowIfFailed (DCompositionCreateDevice (
nullptr, IID_PPV_ARGS (m_dcompDevice.ReleaseAndGetAddressOf ())));
// Create a DirectComposition target associated with the window (pass in hWnd here)
ThrowIfFailed (m_dcompDevice->CreateTargetForHwnd (
container.getHWND (), true, m_dcompTarget.ReleaseAndGetAddressOf ()));
// Create a DirectComposition "visual"
ThrowIfFailed (m_dcompDevice->CreateVisual (m_dcompVisual.ReleaseAndGetAddressOf ()));
// Associate the visual with the swap chain
ThrowIfFailed (m_dcompVisual->SetContent (m_swapChain.Get ()));
// Set the visual as the root of the DirectComposition target's composition tree
ThrowIfFailed (m_dcompTarget->SetRoot (m_dcompVisual.Get ()));
ThrowIfFailed (m_dcompDevice->Commit ());
}
static void getHardwareAdapter (IDXGIFactory2* pFactory, IDXGIAdapter1** ppAdapter)
{
ComPtr<IDXGIAdapter1> adapter;
*ppAdapter = nullptr;
for (UINT adapterIndex = 0;
DXGI_ERROR_NOT_FOUND != pFactory->EnumAdapters1 (adapterIndex, &adapter);
++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1 (&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
// Don't select the Basic Render Driver adapter.
// If you want a software adapter, pass in "/warp" on the command line.
continue;
}
// Check to see if the adapter supports Direct3D 12, but don't create the
// actual device yet.
if (SUCCEEDED (D3D12CreateDevice (adapter.Get (), D3D_FEATURE_LEVEL_11_0,
_uuidof(ID3D12Device), nullptr)))
{
break;
}
}
*ppAdapter = adapter.Detach ();
}
void waitForPreviousFrame ()
{
if (!m_commandQueue || !m_swapChain)
return;
// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
// sample illustrates how to use fences for efficient resource usage and to
// maximize GPU utilization.
m_fence.wait (m_commandQueue.Get ());
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex ();
}
using Mutex = std::recursive_mutex;
using Guard = std::lock_guard<Mutex>;
Mutex mutex;
IntSize m_size {100, 100};
IntRect m_visibleRect {};
double m_scaleFactor {1.};
UINT m_frameIndex {0};
// Synchronization objects.
GPUFence m_fence;
ComPtr<IDXGIFactory4> m_factory;
ComPtr<IDXGISwapChain3> m_swapChain;
ComPtr<ID3D12Device> m_device;
ComPtr<ID3D12CommandQueue> m_commandQueue;
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
// DirectComposition objects.
ComPtr<IDCompositionDevice> m_dcompDevice;
ComPtr<IDCompositionTarget> m_dcompTarget;
ComPtr<IDCompositionVisual> m_dcompVisual;
Direct3D12RendererPtr m_renderer;
};
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,223 @@
// 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 "../lib/vstguibase.h"
#include "../lib/iexternalview.h"
#include <windows.h>
#include <cassert>
#include <functional>
#include <cstdint>
#include <string>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
inline void setWindowSize (HWND window, IntRect r)
{
SetWindowPos (window, HWND_TOP, static_cast<int> (r.origin.x), static_cast<int> (r.origin.y),
static_cast<int> (r.size.width), static_cast<int> (r.size.height),
SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_DEFERERASE);
}
//------------------------------------------------------------------------
struct HWNDWindow final
{
using WindowProcFunc =
std::function<LONG_PTR (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)>;
HWNDWindow (HINSTANCE instance) : instance (instance) {}
~HWNDWindow () noexcept
{
if (window)
{
SetWindowLongPtr (window, GWLP_USERDATA, (__int3264)(LONG_PTR) nullptr);
DestroyWindow (window);
}
destroyWindowClass ();
}
bool create (const TCHAR* title, const IntRect& frame, HWND parent, DWORD exStyle = 0,
DWORD style = WS_CHILD)
{
if (!initWindowClass ())
return false;
window = CreateWindowEx (
exStyle, MAKEINTATOM (windowClassAtom), title, style, static_cast<int> (frame.origin.x),
static_cast<int> (frame.origin.y), static_cast<int> (frame.size.width),
static_cast<int> (frame.size.height), parent, nullptr, instance, nullptr);
if (!window)
return false;
SetWindowLongPtr (window, GWLP_USERDATA, (__int3264)(LONG_PTR)this);
return true;
}
void setWindowProc (WindowProcFunc&& func) { windowProc = std::move (func); }
void setSize (const IntRect& r)
{
if (!window)
return;
setWindowSize (window, r);
}
void show (bool state) { ShowWindow (window, state ? SW_SHOW : SW_HIDE); }
void setEnabled (bool state) { EnableWindow (window, state); }
HWND getHWND () const { return window; }
HINSTANCE getInstance () const { return instance; }
private:
bool initWindowClass ()
{
assert (instance != nullptr);
if (windowClassAtom != 0)
return true;
std::wstring windowClassName;
windowClassName = TEXT ("VSTGUI ExternalView Container ");
windowClassName += std::to_wstring (reinterpret_cast<uint64_t> (this));
WNDCLASS windowClass;
windowClass.style = CS_GLOBALCLASS;
windowClass.lpfnWndProc = WindowProc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = instance;
windowClass.hIcon = 0;
windowClass.hCursor = LoadCursor (NULL, IDC_ARROW);
windowClass.hbrBackground = 0;
windowClass.lpszMenuName = 0;
windowClass.lpszClassName = windowClassName.data ();
windowClassAtom = RegisterClass (&windowClass);
return windowClassAtom != 0;
}
void destroyWindowClass ()
{
if (windowClassAtom == 0)
return;
UnregisterClass (MAKEINTATOM (windowClassAtom), instance);
windowClassAtom = 0;
}
static LONG_PTR WINAPI WindowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_ERASEBKGND)
return 1;
auto instance = reinterpret_cast<HWNDWindow*> (GetWindowLongPtr (hwnd, GWLP_USERDATA));
if (instance && instance->windowProc)
return instance->windowProc (hwnd, message, wParam, lParam);
return DefWindowProc (hwnd, message, wParam, lParam);
}
WindowProcFunc windowProc;
HWND window {nullptr};
HINSTANCE instance {nullptr};
ATOM windowClassAtom {0};
};
//------------------------------------------------------------------------
struct NonClientMetrics
{
static const NONCLIENTMETRICS& get ()
{
static NonClientMetrics gInstance;
return gInstance.nonClientMetrics;
}
private:
NonClientMetrics ()
{
nonClientMetrics.cbSize = sizeof (nonClientMetrics);
SystemParametersInfoForDpi (SPI_GETNONCLIENTMETRICS, nonClientMetrics.cbSize,
&nonClientMetrics, 0, 96);
}
NONCLIENTMETRICS nonClientMetrics {};
};
//------------------------------------------------------------------------
struct ExternalHWNDBase : ViewAdapter
{
using Base = ExternalHWNDBase;
using PlatformViewType = ExternalView::PlatformViewType;
using IntRect = ExternalView::IntRect;
HWNDWindow container;
HWND child {nullptr};
ExternalHWNDBase (HINSTANCE hInst) : container (hInst)
{
container.create (nullptr, {{0, 0}, {1, 1}}, HWND_MESSAGE,
WS_EX_NOPARENTNOTIFY | WS_EX_COMPOSITED, WS_CHILD | WS_VISIBLE);
}
virtual ~ExternalHWNDBase () noexcept
{
if (child)
DestroyWindow (child);
}
bool platformViewTypeSupported (PlatformViewType type) override
{
return type == PlatformViewType::HWND;
}
bool attach (void* parent, PlatformViewType parentViewType) override
{
assert (container.getHWND ());
if (parent == nullptr || parentViewType != PlatformViewType::HWND)
return false;
auto parentHWND = reinterpret_cast<HWND> (parent);
SetParent (container.getHWND (), parentHWND);
return true;
}
bool remove () override
{
assert (container.getHWND ());
SetParent (container.getHWND (), HWND_MESSAGE);
return true;
}
void setViewSize (IntRect frame, IntRect visible) override
{
assert (container.getHWND ());
container.setSize (visible);
if (child)
{
frame.origin.x -= visible.origin.x;
frame.origin.y -= visible.origin.y;
setWindowSize (child, frame);
}
}
void setContentScaleFactor (double scaleFactor) override {}
void setMouseEnabled (bool state) override { EnableWindow (container.getHWND (), state); }
void takeFocus () override { SetFocus (child); }
void looseFocus () override
{
if (GetFocus () == child)
{
SetFocus (GetParent (container.getHWND ()));
}
}
};
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,276 @@
// 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 "externalview_nsview.h"
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#import <functional>
#import <mutex>
//------------------------------------------------------------------------
using VSTGUIMetalLayerDelegateDrawCallback = std::function<void ()>;
using VSTGUIMetalViewScreenChangedCallack = std::function<void (NSScreen*)>;
@interface NSObject ()
- (void)setDrawCallback:(const VSTGUIMetalLayerDelegateDrawCallback&)callback;
- (void)setScreenChangedCallback:(const VSTGUIMetalViewScreenChangedCallack&)callback;
@end
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
struct IMetalView
{
virtual ~IMetalView () noexcept = default;
virtual void render () = 0;
};
//------------------------------------------------------------------------
/** metal render interface to be used as the renderer of the MetalView
*
* The renderer has to set the metal device of the metal layer before it can draw to it.
*/
struct IMetalRenderer
{
virtual ~IMetalRenderer () noexcept = default;
virtual bool init (IMetalView* metalView, CAMetalLayer* metalLayer) = 0;
virtual void draw (id<CAMetalDrawable> drawable) = 0;
virtual void onSizeUpdate (int32_t width, int32_t height, double scaleFactor) = 0;
virtual void onAttached () = 0;
virtual void onRemoved () = 0;
virtual void onScreenChanged (NSScreen* screen) = 0;
};
using MetalRendererPtr = std::shared_ptr<IMetalRenderer>;
//------------------------------------------------------------------------
struct MetalLayerDelegate : RuntimeObjCClass<MetalLayerDelegate>
{
static constexpr auto CallbackVarName = "callback";
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("MetalLayerDelegate", [NSObject class])
.addMethod (@selector (displayLayer:), displayLayer)
.addMethod (@selector (actionForLayer:forKey:), actionForLayer)
.addMethod (@selector (setDrawCallback:), setCallback)
.addProtocol ("CALayerDelegate")
.addIvar<VSTGUIMetalLayerDelegateDrawCallback> (CallbackVarName)
.finalize ();
}
static void setCallback (id self, SEL cmd, VSTGUIMetalLayerDelegateDrawCallback callback)
{
auto instance = makeInstance (self);
if (auto var = instance.getVariable<VSTGUIMetalLayerDelegateDrawCallback> (CallbackVarName))
var->set (callback);
}
static void displayLayer (id self, SEL cmd, CALayer* layer)
{
auto instance = makeInstance (self);
if (auto var = instance.getVariable<VSTGUIMetalLayerDelegateDrawCallback> (CallbackVarName))
{
if (auto callback = var->get ())
callback ();
}
}
static id<CAAction> actionForLayer (CALayer* layer, NSString* key) { return [NSNull null]; }
};
//------------------------------------------------------------------------
struct MetalNSView : RuntimeObjCClass<MetalNSView>
{
static constexpr auto CallbackVarName = "callback";
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("MetalNSView", [NSView class])
.addIvar<VSTGUIMetalViewScreenChangedCallack> (CallbackVarName)
.addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow)
.addMethod (@selector (viewWillMoveToWindow:), viewWillMoveToWindow)
.addMethod (@selector (windowDidChangeScreen:), windowDidChangeScreen)
.addMethod (@selector (setScreenChangedCallback:), setCallback)
.finalize ();
}
static void setCallback (id self, SEL cmd, VSTGUIMetalViewScreenChangedCallack callback)
{
auto instance = makeInstance (self);
if (auto var = instance.getVariable<VSTGUIMetalViewScreenChangedCallack> (CallbackVarName))
var->set (callback);
}
static void viewDidMoveToWindow (id self, SEL cmd)
{
windowDidChangeScreen (self, cmd, nullptr);
makeInstance (self).callSuper<void ()> (cmd);
}
static void viewWillMoveToWindow (id self, SEL cmd, NSWindow* window)
{
if (auto prevWindow = [self window])
{
[NSNotificationCenter.defaultCenter removeObserver:self];
}
if (window)
{
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector (windowDidChangeScreen:)
name:NSWindowDidChangeScreenNotification
object:window];
}
makeInstance (self).callSuper<void (NSWindow*)> (cmd, window);
}
static void windowDidChangeScreen (id self, SEL cmd, NSNotification* n)
{
if (NSScreen* screen = [[self window] screen])
{
auto instance = makeInstance (self);
if (auto var =
instance.getVariable<VSTGUIMetalViewScreenChangedCallack> (CallbackVarName))
{
if (auto callback = var->get ())
callback (screen);
}
}
}
};
//------------------------------------------------------------------------
struct MetalView : ExternalNSViewBase<NSView>,
IMetalView
{
/** make a new metal view.
*
* The metal view can render on a background thread (only use one thread for rendering) or on
* the main thread.
* Rendering and view resizing is automatically guarded by a mutex.
* The view will automatically trigger a rendering when the view is resized.
*/
static std::shared_ptr<MetalView> make (const MetalRendererPtr& renderer)
{
if (!renderer)
return {};
if (auto metalView = std::shared_ptr<MetalView> (new MetalView (renderer)))
{
if (renderer->init (metalView.get (), metalView->metalLayer))
return metalView;
}
return {};
}
/** immediately render the view [thread safe] */
void render () override
{
doLocked ([&] () { renderer->draw (metalLayer.nextDrawable); });
}
/** do something locked [thread safe] */
template<typename Proc>
void doLocked (Proc proc)
{
LockGuard g (mutex);
@autoreleasepool
{
proc ();
}
}
private:
CAMetalLayer* metalLayer {nullptr};
id metalLayerDelegate {nullptr};
double contentScaleFactor {1.};
using Mutex = std::recursive_mutex;
using LockGuard = std::lock_guard<Mutex>;
Mutex mutex;
MetalRendererPtr renderer;
MetalView (const MetalRendererPtr& renderer)
: Base ([MetalNSView::alloc () init]), renderer (renderer)
{
metalLayerDelegate = [MetalLayerDelegate::alloc () init];
metalLayer = [CAMetalLayer new];
metalLayer.delegate = metalLayerDelegate;
view.layer = metalLayer;
metalLayer.needsDisplayOnBoundsChange = YES;
metalLayer.geometryFlipped = YES;
metalLayer.opaque = NO;
metalLayer.contentsGravity = kCAGravityBottomLeft;
[metalLayerDelegate setDrawCallback:[this] () {
render ();
}];
[view setScreenChangedCallback:[this] (NSScreen* screen) {
this->renderer->onScreenChanged (screen);
}];
}
bool attach (void* parent, PlatformViewType parentViewType) override
{
if (Base::attach (parent, parentViewType))
{
renderer->onAttached ();
return true;
}
return false;
}
bool remove () override
{
if (Base::remove ())
{
renderer->onRemoved ();
return true;
}
return false;
}
void setContentScaleFactor (double scaleFactor) override
{
contentScaleFactor = scaleFactor;
metalLayer.contentsScale = scaleFactor;
[metalLayer setNeedsDisplay];
onSizeUpdate ();
}
void setViewSize (IntRect frame, IntRect visible) override
{
Base::setViewSize (frame, visible);
onSizeUpdate ();
}
void onSizeUpdate ()
{
doLocked ([this] () {
auto size = view.frame.size;
metalLayer.drawableSize =
NSMakeSize (size.width * contentScaleFactor, size.height * contentScaleFactor);
renderer->onSizeUpdate (size.width, size.height, contentScaleFactor);
});
}
#if !__has_feature(objc_arc)
public:
~MetalView () noexcept override
{
[metalLayerDelegate release];
[metalLayer release];
}
#endif
};
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
@@ -0,0 +1,249 @@
// 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 "../lib/platform/mac/cocoa/objcclassbuilder.h"
#import "../lib/iexternalview.h"
#import <Cocoa/Cocoa.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ExternalView {
//------------------------------------------------------------------------
inline NSRect toNSRect (const IntRect& r)
{
return NSMakeRect (r.origin.x, r.origin.y, r.size.width, r.size.height);
}
//------------------------------------------------------------------------
/** a NSView that has a flipped coordinate system (top-left is {0, 0}, not AppKits default which is
* bottom-left)
*
* to create it call [ExternalViewContainerNSView::alloc () initWithFrame: rect]
*/
struct ExternalViewContainerNSView : RuntimeObjCClass<ExternalViewContainerNSView>
{
static constexpr auto TookFocusCallbackVarName = "TookFocusCallback";
static Class CreateClass ()
{
return ObjCClassBuilder ()
.init ("ExternalViewContainerNSView", [NSView class])
.addMethod (@selector (isFlipped), isFlipped)
.addMethod (@selector (viewWillMoveToWindow:), viewWillMoveToWindow)
.addMethod (@selector (observeValueForKeyPath:ofObject:change:context:),
observeValueForKeyPath)
.addIvar<IView::TookFocusCallback> (TookFocusCallbackVarName)
.finalize ();
}
static BOOL isFlipped (id self, SEL cmd) { return YES; }
static void viewWillMoveToWindow (id self, SEL _cmd, NSWindow* window)
{
if ([self window] && [self window] != window)
{
[[self window] removeObserver:self forKeyPath:@"firstResponder"];
}
if (window)
{
[window addObserver:self forKeyPath:@"firstResponder" options:0 context:nullptr];
}
}
static void observeValueForKeyPath (id self, SEL cmd, NSString* keyPath, id object,
NSDictionary<NSKeyValueChangeKey, id>* change,
void* context)
{
if ([keyPath isEqualToString:@"firstResponder"])
{
auto view = [self window].firstResponder;
if ([view isKindOfClass:[NSView class]] &&
[static_cast<NSView*> (view) isDescendantOf:self])
{
if (auto var = makeInstance (self).getVariable<IView::TookFocusCallback> (
TookFocusCallbackVarName))
{
if (var.value ().get ())
var.value ().get () ();
}
}
}
}
};
//------------------------------------------------------------------------
/** a template helper class for embedding NSViews into VSTGUI via ExternalView::IView
*
* Example to add a simple NSView:
*
* // Header: ExampleNSView.h
*
* class ExampleNSView : IView
* {
* public:
* ExampleNSView ();
* ~ExampleNSView () noexcept;
*
* private:
* bool platformViewTypeSupported (PlatformViewType type) override;
* bool attach (void* parent, PlatformViewType parentViewType) override;
* bool remove () override;
* void setViewSize (IntRect frame, IntRect visible) override;
* void setContentScaleFactor (double scaleFactor) override;
* void setMouseEnabled (bool state) override;
* void takeFocus () override;
* void looseFocus () override;
*
* struct Impl;
* std::unique_ptr<Impl> impl;
* };
*
* // Source: ExampleNSView.mm
*
* #import "ExampleNSView.h"
* #import "externalview_nsview.h"
*
* struct ExampleNSView::Impl : ExternalNSViewBase<NSView>
* {
* Impl () : Base ([NSView new])
* {
* // configure the view here
* view.alphaValue = 0.5;
* }
* };
*
* ExampleNSView::ExampleNSView () { impl = std::make_unique<Impl> (); }
* ExampleNSView::~ExampleNSView () noexcept = default;
* bool ExampleNSView::platformViewTypeSupported (PlatformViewType type)
* {
* return impl->platformViewTypeSupported (type);
* }
* bool ExampleNSView::attach (void* parent, PlatformViewType parentViewType)
* {
* return impl->attach (parent, parentViewType);
* }
* bool ExampleNSView::remove () { return impl->remove (); }
* void ExampleNSView::setViewSize (IntRect frame, IntRect visible)
* {
* impl->setViewSize (frame, visible);
* }
* void ExampleNSView::setContentScaleFactor (double scaleFactor)
* {
* impl->setContentScaleFactor (scaleFactor);
* }
* void ExampleNSView::setMouseEnabled (bool state) { impl->setMouseEnabled (state); }
* void ExampleNSView::takeFocus () { impl->takeFocus (); }
* void ExampleNSView::looseFocus () { impl->looseFocus (); }
*
*/
template<typename ViewType>
struct ExternalNSViewBase : ViewAdapter
{
using Base = ExternalNSViewBase<ViewType>;
using PlatformViewType = ExternalView::PlatformViewType;
using IntRect = ExternalView::IntRect;
NSView* container {
[ExternalViewContainerNSView::alloc () initWithFrame: {{0., 0.}, {10., 10.}}]};
ViewType* view {nullptr};
ExternalNSViewBase (ViewType* inView) : view (inView)
{
if (@available (macOS 14, *))
{
#ifdef MAC_OS_VERSION_14_0
// only available when building with the mac os sdk 14.0
container.clipsToBounds = YES;
#else
// but necessary to set to YES on macOS 14 even when not building with Xcode 15
if ([container respondsToSelector:@selector (setClipsToBounds:)])
{
BOOL clipsToBounds = YES;
auto* signature = [[container class]
instanceMethodSignatureForSelector:@selector (setClipsToBounds:)];
auto* invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = container;
invocation.selector = @selector (setClipsToBounds:);
[invocation setArgument:&clipsToBounds atIndex:2];
[invocation invoke];
}
#endif
}
[container addSubview:view];
}
#if !__has_feature(objc_arc)
virtual ~ExternalNSViewBase () noexcept
{
[container release];
[view release];
}
#endif
bool platformViewTypeSupported (PlatformViewType type) override
{
return type == PlatformViewType::NSView;
}
bool attach (void* parent, PlatformViewType parentViewType) override
{
if (!parent || parentViewType != PlatformViewType::NSView)
return false;
auto parentNSView = (__bridge NSView*)parent;
[parentNSView addSubview:container];
return true;
}
bool remove () override
{
[container removeFromSuperview];
return true;
}
void setViewSize (IntRect frame, IntRect visible) override
{
container.frame = toNSRect (visible);
frame.origin.x -= visible.origin.x;
frame.origin.y -= visible.origin.y;
view.frame = toNSRect (frame);
}
void setContentScaleFactor (double scaleFactor) override {}
void setMouseEnabled (bool state) override
{
if ([view respondsToSelector:@selector (setEnabled:)])
[(id)view setEnabled:state];
}
void takeFocus () override
{
if (view.acceptsFirstResponder)
{
if (auto window = view.window)
[window makeFirstResponder:view];
}
}
void looseFocus () override
{
if (auto window = view.window)
[window makeFirstResponder:container.superview];
}
void setTookFocusCallback (const TookFocusCallback& callback) override
{
if (auto var = ObjCInstance (container).getVariable<TookFocusCallback> (
ExternalViewContainerNSView::TookFocusCallbackVarName))
{
var->set (callback);
}
}
};
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,269 @@
//------------------------------------------------------------------------
// 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
// Flags : clang-format SMTGSequencer
#pragma once
#include "vstgui/lib/vstguifwd.h"
#include "vstgui/lib/ccolor.h"
#include "vstgui/lib/cview.h"
#include "vstgui/lib/dispatchlist.h"
#include "vstgui/lib/itouchevent.h"
#include <array>
#include <bitset>
#include <map>
namespace VSTGUI {
//------------------------------------------------------------------------
class KeyboardViewBase : public CView
{
public:
using NoteIndex = int16_t;
using NumNotes = uint8_t;
static constexpr NumNotes MaxNotes = 128;
enum class BitmapID
{
WhiteKeyPressed = 0,
WhiteKeyUnpressed,
BlackKeyPressed,
BlackKeyUnpressed,
WhiteKeyShadowLeft,
WhiteKeyShadowRight,
NumBitmaps
};
KeyboardViewBase ();
void setKeyPressed (NoteIndex note, bool state);
virtual void setKeyRange (NoteIndex startNote, NumNotes numKeys);
NoteIndex getKeyRangeStart () const { return startNote; }
NumNotes getNumKeys () const { return numKeys; }
NumNotes getNumWhiteKeys () const;
void setWhiteKeyWidth (CCoord width);
void setBlackKeyWidth (CCoord width);
void setBlackKeyHeight (CCoord height);
void setLineWidth (CCoord width);
CCoord getWhiteKeyWidth () const { return whiteKeyWidth; }
CCoord getBlackKeyWidth () const { return blackKeyWidth; }
CCoord getBlackKeyHeight () const { return blackKeyHeight; }
CCoord getLineWidth () const { return lineWidth; }
void setFrameColor (CColor color);
void setFontColor (CColor color);
void setWhiteKeyColor (CColor color);
void setWhiteKeyPressedColor (CColor color);
void setBlackKeyColor (CColor color);
void setBlackKeyPressedColor (CColor color);
CColor getFrameColor () const { return frameColor; }
CColor getFontColor () const { return fontColor; }
CColor getWhiteKeyColor () const { return whiteKeyColor; }
CColor getWhiteKeyPressedColor () const { return whiteKeyPressedColor; }
CColor getBlackKeyColor () const { return blackKeyColor; }
CColor getBlackKeyPressedColor () const { return blackKeyPressedColor; }
void setNoteNameFont (CFontDesc* font);
CFontDesc* getNoteNameFont () const { return noteNameFont; }
void setDrawNoteText (bool state);
bool getDrawNoteText () const { return drawNoteText; }
void setBitmap (BitmapID bID, CBitmap* bitmap);
CBitmap* getBitmap (BitmapID bID) const;
void setWhiteKeyBitmapInset (const CRect& inset); // TODO: uidesc
void setBlackKeyBitmapInset (const CRect& inset); // TODO: uidesc
const CRect& getNoteRect (NoteIndex note) const { return noteRectCache[note]; }
bool isWhiteKey (NoteIndex note) const;
void drawRect (CDrawContext* context, const CRect& dirtyRect) override;
void setViewSize (const CRect& rect, bool invalid = true) override;
bool sizeToFit () override;
//------------------------------------------------------------------------
protected:
using NoteRectCache = std::array<CRect, MaxNotes>;
void invalidNote (NoteIndex note);
NoteIndex pointToNote (const CPoint& p, bool ignoreY) const;
const NoteRectCache& getNoteRectCache () const { return noteRectCache; }
private:
void drawNote (CDrawContext* context, CRect& rect, NoteIndex note, bool isWhite) const;
CRect calcNoteRect (NoteIndex note) const;
void updateNoteRectCache () const;
void createBitmapCache ();
using BitmapArray =
std::array<SharedPointer<CBitmap>, static_cast<size_t> (BitmapID::NumBitmaps)>;
BitmapArray bitmaps;
SharedPointer<CBitmap> whiteKeyBitmapCache;
SharedPointer<CBitmap> blackKeyBitmapCache;
SharedPointer<CFontDesc> noteNameFont;
CRect whiteKeyBitmapInset;
CRect blackKeyBitmapInset;
CCoord whiteKeyWidth {30};
CCoord blackKeyWidth {20};
CCoord blackKeyHeight {20};
CCoord lineWidth {1.};
CColor frameColor {kBlackCColor};
CColor fontColor {kBlackCColor};
CColor whiteKeyColor {kWhiteCColor};
CColor whiteKeyPressedColor {kGreyCColor};
CColor blackKeyColor {kBlackCColor};
CColor blackKeyPressedColor {kGreyCColor};
NumNotes numKeys {88};
NoteIndex startNote {21};
bool drawNoteText {false};
mutable bool noteRectCacheInvalid {true};
mutable NoteRectCache noteRectCache;
std::bitset<MaxNotes> keyPressed {};
};
class KeyboardViewRangeSelector;
//------------------------------------------------------------------------
struct IKeyboardViewKeyRangeChangedListener
{
virtual void onKeyRangeChanged (KeyboardViewRangeSelector*) = 0;
virtual ~IKeyboardViewKeyRangeChangedListener () noexcept = default;
};
//------------------------------------------------------------------------
class KeyboardViewRangeSelector : public KeyboardViewBase
{
public:
struct Range
{
NoteIndex position;
NumNotes length;
Range (NoteIndex position = 0, NumNotes length = 0) : position (position), length (length)
{
}
bool operator!= (const Range& r) const
{
return position != r.position || length != r.length;
}
};
KeyboardViewRangeSelector () = default;
void drawRect (CDrawContext* context, const CRect& dirtyRect) override;
void setKeyRange (NoteIndex startNote, NumNotes numKeys) override;
void setSelectionRange (const Range& range);
void setSelectionMinMax (NumNotes minRange, NumNotes maxRange);
const Range& getSelectionRange () const { return selectionRange; }
NumNotes getSelectionMin () const { return rangeMin; }
NumNotes getSelectionMax () const { return rangeMax; }
NumNotes getNumWhiteKeysSelected () const;
void registerKeyRangeChangedListener (IKeyboardViewKeyRangeChangedListener* listener);
void unregisterKeyRangeChangedListener (IKeyboardViewKeyRangeChangedListener* listener);
//------------------------------------------------------------------------
private:
DispatchList<IKeyboardViewKeyRangeChangedListener*> listeners;
Range selectionRange {0, 12};
NumNotes rangeMin {12};
NumNotes rangeMax {24};
#if VSTGUI_TOUCH_EVENT_HANDLING
void onTouchEvent (ITouchEvent& event) override;
bool wantsMultiTouchEvents () const override;
void onTouchBegin (const ITouchEvent::TouchPair& touch, ITouchEvent& event);
void onTouchMove (const ITouchEvent::TouchPair& touch, ITouchEvent& event);
enum TouchMode {kUnknown, kMoveRange, kChangeRangeFront, kChangeRangeBack};
Range selectionRangeOnTouchStart;
int32_t touchIds[2] {-1};
TouchMode touchMode {kUnknown};
NoteIndex touchStartNote[2];
#else
CMouseEventResult onMouseDown (CPoint& where, const CButtonState& buttons) override;
CMouseEventResult onMouseMoved (CPoint& where, const CButtonState& buttons) override;
CMouseEventResult onMouseUp (CPoint& where, const CButtonState& buttons) override;
CMouseEventResult onMouseCancel () override;
Range moveStartRange;
NoteIndex moveStartNote {-1};
#endif
};
//------------------------------------------------------------------------
struct IKeyboardViewPlayerDelegate
{
using NoteIndex = KeyboardViewBase::NoteIndex;
virtual int32_t onNoteOn (NoteIndex note, double xPos, double yPos) = 0;
virtual void onNoteOff (NoteIndex note, int32_t noteID) = 0;
virtual void onNoteModulation (int32_t noteID, double xPos, double yPos) = 0;
virtual ~IKeyboardViewPlayerDelegate () noexcept = default;
};
//------------------------------------------------------------------------
struct KeyboardViewPlayerDelegate : public IKeyboardViewPlayerDelegate
{
int32_t onNoteOn (NoteIndex note, double xPos, double yPos) override { return -1; }
void onNoteOff (NoteIndex note, int32_t noteID) override {}
void onNoteModulation (int32_t noteID, double xPos, double yPos) override {}
};
//------------------------------------------------------------------------
class KeyboardView : public KeyboardViewBase
{
public:
KeyboardView ();
void setDelegate (IKeyboardViewPlayerDelegate* inDelegate) { delegate = inDelegate; }
private:
double calcYParameter (NoteIndex note, CCoord y) const;
double calcXParameter (NoteIndex note, CCoord x) const;
#if VSTGUI_TOUCH_EVENT_HANDLING
bool wantsMultiTouchEvents () const override { return true; }
void onTouchEvent (ITouchEvent& event) override;
void onTouchBegin (const ITouchEvent::TouchPair& touch, ITouchEvent& event);
void onTouchMove (const ITouchEvent::TouchPair& touch, ITouchEvent& event);
void onTouchEnd (const ITouchEvent::TouchPair& touch, ITouchEvent& event);
struct NoteTouch
{
NoteIndex note;
int32_t noteID;
NoteTouch (NoteIndex note) : note (note), noteID (-1) {}
};
std::map<int32_t, NoteTouch> noteTouches;
#else
void doNoteOff ();
void doNoteOn (NoteIndex note, double yPos, double xPos);
CMouseEventResult onMouseDown (CPoint& where, const CButtonState& buttons) override;
CMouseEventResult onMouseMoved (CPoint& where, const CButtonState& buttons) override;
CMouseEventResult onMouseUp (CPoint& where, const CButtonState& buttons) override;
CMouseEventResult onMouseCancel () override;
NoteIndex pressedNote {-1};
int32_t noteID {-1};
#endif
IKeyboardViewPlayerDelegate* delegate {nullptr};
};
//------------------------------------------------------------------------
} // VSTGUI