Initial release
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user