diff --git a/vstgui/contrib/datepicker.h b/vstgui/contrib/datepicker.h new file mode 100644 index 000000000..7d6346e7d --- /dev/null +++ b/vstgui/contrib/datepicker.h @@ -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 +#include + +//------------------------------------------------------------------------ +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 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; +}; + +//------------------------------------------------------------------------ +} // ExternalView +} // VSTGUI diff --git a/vstgui/contrib/datepicker.mm b/vstgui/contrib/datepicker.mm new file mode 100644 index 000000000..aa1a4a245 --- /dev/null +++ b/vstgui/contrib/datepicker.mm @@ -0,0 +1,196 @@ +// 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 +{ + using DoneCallback = std::function; + using ValidateCallback = std::function; + + 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 (ValidateCallbackVarName) + .addIvar (DoneCallbackVarName) + .finalize (); + } + + static id initWithCallbacks (id self, DoneCallback&& doneCallback, ValidateCallback&& callback) + { + if ((self = makeInstance (self).callSuper (@selector (init)))) + { + auto instance = makeInstance (self); + if (auto var = instance.getVariable (DoneCallbackVarName)) + var->set (doneCallback); + if (auto var = instance.getVariable (ValidateCallbackVarName)) + var->set (callback); + } + return self; + } + + static void complete (id self, SEL cmd, id sender) + { + if (auto var = makeInstance (self).getVariable (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 (ValidateCallbackVarName)) + { + const auto& callback = var->get (); + if (callback) + callback (proposedDateValue, proposedTimeInterval); + } + } +}; + +//------------------------------------------------------------------------ +struct DatePicker::Impl : ExternalNSViewBase +{ + 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 ([[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 (components.day); + date.month = static_cast (components.month); + date.year = static_cast (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]; +} + +//------------------------------------------------------------------------ +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 diff --git a/vstgui/contrib/datepicker_win32.cpp b/vstgui/contrib/datepicker_win32.cpp new file mode 100644 index 000000000..8dda2c060 --- /dev/null +++ b/vstgui/contrib/datepicker_win32.cpp @@ -0,0 +1,132 @@ + +#include "datepicker.h" +#include "externalview_hwnd.h" +#include "vstgui/lib/platform/win32/win32factory.h" + +#include + +//------------------------------------------------------------------------ +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 (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 (lParam); + switch (hdr->code) + { + case DTN_DATETIMECHANGE: + { + LPNMDATETIMECHANGE lpChange = reinterpret_cast (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 (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 diff --git a/vstgui/contrib/evbutton.h b/vstgui/contrib/evbutton.h new file mode 100644 index 000000000..ce06968ca --- /dev/null +++ b/vstgui/contrib/evbutton.h @@ -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 + +//------------------------------------------------------------------------ +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; +}; + +//------------------------------------------------------------------------ +} // ExternalView +} // VSTGUI diff --git a/vstgui/contrib/evbutton_macos.mm b/vstgui/contrib/evbutton_macos.mm new file mode 100644 index 000000000..09ebbe95f --- /dev/null +++ b/vstgui/contrib/evbutton_macos.mm @@ -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 +{ + using ActionCallback = std::function; + + 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 (ActionCallbackVarName) + .finalize (); + } + + static id initWithCallbacks (id self, ActionCallback&& actionCallback) + { + if ((self = makeInstance (self).callSuper (@selector (init)))) + { + auto instance = makeInstance (self); + if (auto var = instance.getVariable (ActionCallbackVarName)) + var->set (actionCallback); + } + return self; + } + + static void onAction (id self, SEL cmd, id sender) + { + if (auto var = makeInstance (self).getVariable (ActionCallbackVarName)) + { + const auto& callback = var->get (); + if (callback) + callback (); + } + } +}; + +//------------------------------------------------------------------------ +struct Button::Impl : ExternalNSViewBase, + 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 (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 (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 diff --git a/vstgui/contrib/evbutton_win32.cpp b/vstgui/contrib/evbutton_win32.cpp new file mode 100644 index 000000000..8483a6d65 --- /dev/null +++ b/vstgui/contrib/evbutton_win32.cpp @@ -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 + +//------------------------------------------------------------------------ +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 (inTitle.getPlatformString ()); + auto hInstance = getPlatformFactory ().asWin32Factory ()->getInstance (); + impl = std::make_unique (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 diff --git a/vstgui/contrib/externalview_direct3d12.h b/vstgui/contrib/externalview_direct3d12.h new file mode 100644 index 000000000..2102146ac --- /dev/null +++ b/vstgui/contrib/externalview_direct3d12.h @@ -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 +#include +#include +#include +#include + +#include + +#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; + +//------------------------------------------------------------------------ +struct GPUFence +{ + template + using ComPtr = Microsoft::WRL::ComPtr; + + 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 m_fence; + UINT64 m_value {0}; +}; + +//------------------------------------------------------------------------ +struct Direct3D12View : public ExternalHWNDBase, + IDirect3D12View +{ + template + using ComPtr = Microsoft::WRL::ComPtr; + + Direct3D12View (HINSTANCE instance, const Direct3D12RendererPtr& renderer, + ComPtr factory = nullptr, ComPtr device = nullptr, ComPtr 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 make (HINSTANCE instance, + const Direct3D12RendererPtr& renderer, + ComPtr factory = nullptr, + ComPtr device = nullptr, + ComPtr queue = nullptr) + { + return std::make_shared (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 (m_size.width), + static_cast (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 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 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 (m_size.width); + swapChainDesc.Height = static_cast (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 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 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; + + IntSize m_size {100, 100}; + IntRect m_visibleRect {}; + double m_scaleFactor {1.}; + + UINT m_frameIndex {0}; + + // Synchronization objects. + GPUFence m_fence; + + ComPtr m_factory; + + ComPtr m_swapChain; + + ComPtr m_device; + ComPtr m_commandQueue; + ComPtr m_commandAllocator; + + // DirectComposition objects. + ComPtr m_dcompDevice; + ComPtr m_dcompTarget; + ComPtr m_dcompVisual; + + Direct3D12RendererPtr m_renderer; +}; + +//------------------------------------------------------------------------ +} // ExternalView +} // VSTGUI diff --git a/vstgui/contrib/externalview_hwnd.h b/vstgui/contrib/externalview_hwnd.h new file mode 100644 index 000000000..acb7367ac --- /dev/null +++ b/vstgui/contrib/externalview_hwnd.h @@ -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 "vstgui/lib/vstguibase.h" +#include "vstgui/lib/iexternalview.h" + +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace ExternalView { + +//------------------------------------------------------------------------ +inline void setWindowSize (HWND window, IntRect r) +{ + SetWindowPos (window, HWND_TOP, static_cast (r.origin.x), static_cast (r.origin.y), + static_cast (r.size.width), static_cast (r.size.height), + SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_DEFERERASE); +} + +//------------------------------------------------------------------------ +struct HWNDWindow final +{ + using WindowProcFunc = + std::function; + + 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 (frame.origin.x), + static_cast (frame.origin.y), static_cast (frame.size.width), + static_cast (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 (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 (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 (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 diff --git a/vstgui/contrib/externalview_metal.h b/vstgui/contrib/externalview_metal.h new file mode 100644 index 000000000..e0aa06c15 --- /dev/null +++ b/vstgui/contrib/externalview_metal.h @@ -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 +#import +#import +#import + +//------------------------------------------------------------------------ +using VSTGUIMetalLayerDelegateDrawCallback = std::function; +using VSTGUIMetalViewScreenChangedCallack = std::function; + +@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 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; + +//------------------------------------------------------------------------ +struct MetalLayerDelegate : RuntimeObjCClass +{ + 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 (CallbackVarName) + .finalize (); + } + + static void setCallback (id self, SEL cmd, VSTGUIMetalLayerDelegateDrawCallback callback) + { + auto instance = makeInstance (self); + if (auto var = instance.getVariable (CallbackVarName)) + var->set (callback); + } + + static void displayLayer (id self, SEL cmd, CALayer* layer) + { + auto instance = makeInstance (self); + if (auto var = instance.getVariable (CallbackVarName)) + { + if (auto callback = var->get ()) + callback (); + } + } + + static id actionForLayer (CALayer* layer, NSString* key) { return [NSNull null]; } +}; + +//------------------------------------------------------------------------ +struct MetalNSView : RuntimeObjCClass +{ + static constexpr auto CallbackVarName = "callback"; + + static Class CreateClass () + { + return ObjCClassBuilder () + .init ("MetalNSView", [NSView class]) + .addIvar (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 (CallbackVarName)) + var->set (callback); + } + + static void viewDidMoveToWindow (id self, SEL cmd) + { + windowDidChangeScreen (self, cmd, nullptr); + makeInstance (self).callSuper (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 (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 (CallbackVarName)) + { + if (auto callback = var->get ()) + callback (screen); + } + } + } +}; + +//------------------------------------------------------------------------ +struct MetalView : ExternalNSViewBase, + 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 make (const MetalRendererPtr& renderer) + { + if (!renderer) + return {}; + if (auto metalView = std::shared_ptr (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 + 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; + 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 diff --git a/vstgui/contrib/externalview_nsview.h b/vstgui/contrib/externalview_nsview.h new file mode 100644 index 000000000..2e11a76dd --- /dev/null +++ b/vstgui/contrib/externalview_nsview.h @@ -0,0 +1,225 @@ +// 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 + +//------------------------------------------------------------------------ +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 +{ + 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 (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* change, + void* context) + { + if ([keyPath isEqualToString:@"firstResponder"]) + { + auto view = [self window].firstResponder; + if ([view isKindOfClass:[NSView class]] && + [static_cast (view) isDescendantOf:self]) + { + if (auto var = makeInstance (self).getVariable ( + 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; + * }; + * + * // Source: ExampleNSView.mm + * + * #import "ExampleNSView.h" + * #import "externalview_nsview.h" + * + * struct ExampleNSView::Impl : ExternalNSViewBase + * { + * Impl () : Base ([NSView new]) + * { + * // configure the view here + * view.alphaValue = 0.5; + * } + * }; + * + * ExampleNSView::ExampleNSView () { impl = std::make_unique (); } + * 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 +struct ExternalNSViewBase : ViewAdapter +{ + using Base = ExternalNSViewBase; + 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) { [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 ( + ExternalViewContainerNSView::TookFocusCallbackVarName)) + { + var->set (callback); + } + } +}; + +//------------------------------------------------------------------------ +} // ExternalView +} // VSTGUI diff --git a/vstgui/doxygen/page_changes.h b/vstgui/doxygen/page_changes.h index 47221c3d4..fb35ba5d6 100644 --- a/vstgui/doxygen/page_changes.h +++ b/vstgui/doxygen/page_changes.h @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -22,6 +22,10 @@ It's recommended to start new projects with version 4 while old projects should @section new_stuff New Stuff +@subsection version4_13 Version 4.13 + +- support embedding platform views (HWND & NSView) as sub views (see CExternalView and ExternalView::IView) and examples in the contrib folder. + @subsection version4_12_2 Version 4.12.2 - make it possible to draw only frames in a range for views using multi frame bitmaps. @@ -83,7 +87,7 @@ it, while the old method is deprecated but still supported for now. - preview of @ref standalone_library @n - new Controls: VSTGUI::CMultiLineTextLabel, VSTGUI::CSearchTextEdit - adopt many c++11 language features - + @subsection version4_4 Version 4.4 - preview Linux version @@ -132,6 +136,10 @@ Note: All current deprecated methods will be removed in the next version. So mak @section code_changes Changes for existing VSTGUI code +@subsection code_changes_4_12_to_4_13 VSTGUI 4.12 -> VSTGUI 4.13 + +- the context argument of IFontPainter has changed to use the new platform graphics device context + @subsection code_changes_4_11_to_4_12 VSTGUI 4.11 -> VSTGUI 4.12 - The CMultiFrameBitmap change deprecated the VSTGUI::IMultiBitmapControl class. If you use it, @@ -266,7 +274,7 @@ The old mouse methods (onMouseDown, onMouseUp, onMouseMoved, etc) are still supp @section hidpi_support HiDPI notes - HiDPI is supported on OSX, iOS and Windows (with Direct2D backend) - Due to platform differences one need to call frame->setZoom (scaleFactor) on Windows, while on OSX and iOS this is not needed. - + @section ios_support iOS support notes - VSTGUI supports iOS 7 and later @@ -274,10 +282,10 @@ The old mouse methods (onMouseDown, onMouseUp, onMouseMoved, etc) are still supp - Support for a single MultiTouch View is not yet tested and the API may change in the future @page page_previous_new_stuff New Stuff in VSTGUI 3.6 and earlier - + @section new_mouse_methods New mouse methods -In earlier versions there were only one method in CView for handling mouse events (VSTGUI::CView::mouse). +In earlier versions there were only one method in CView for handling mouse events (VSTGUI::CView::mouse). In this version there are five new methods : - VSTGUI::CView::onMouseDown (new in 3.5) - VSTGUI::CView::onMouseUp (new in 3.5) diff --git a/vstgui/lib/CMakeLists.txt b/vstgui/lib/CMakeLists.txt index 7d13703c2..79e4fb44d 100644 --- a/vstgui/lib/CMakeLists.txt +++ b/vstgui/lib/CMakeLists.txt @@ -3,6 +3,16 @@ ########################################################################################## set(target vstgui) +option( + VSTGUI_TEXTRENDERING_LEGACY_INCONSISTENCY + "Use the legacy platform inconsistency text rendering" + OFF +) + +if(${VSTGUI_TEXTRENDERING_LEGACY_INCONSISTENCY}) + target_compile_definitions(${target} PRIVATE VSTGUI_TEXTRENDERING_LEGACY_INCONSISTENCY=1) +endif() + set(${target}_common_sources animation/animations.cpp animation/animations.h @@ -31,6 +41,8 @@ set(${target}_common_sources cdrawmethods.h cdropsource.cpp cdropsource.h + cexternalview.cpp + cexternalview.h cfileselector.cpp cfileselector.h cfont.cpp @@ -139,6 +151,7 @@ set(${target}_common_sources idatabrowserdelegate.h idatapackage.h idependency.h + iexternalview.h ifocusdrawing.h iscalefactorchangedlistener.h itouchevent.h @@ -148,6 +161,7 @@ set(${target}_common_sources pixelbuffer.h pixelbuffer.cpp platform/iplatformbitmap.h + platform/iplatformgraphicsdevice.h platform/iplatformfileselector.h platform/iplatformfont.h platform/iplatformframe.h @@ -193,8 +207,6 @@ set(${target}_mac_sources platform/mac/cfontmac.mm platform/mac/cgbitmap.cpp platform/mac/cgbitmap.h - platform/mac/cgdrawcontext.cpp - platform/mac/cgdrawcontext.h platform/mac/cocoa/autoreleasepool.h platform/mac/cocoa/autoreleasepool.mm platform/mac/cocoa/cocoahelpers.h @@ -210,6 +222,8 @@ set(${target}_mac_sources platform/mac/cocoa/nsviewoptionmenu.h platform/mac/cocoa/nsviewoptionmenu.mm platform/mac/cocoa/objcclassbuilder.h + platform/mac/coregraphicsdevicecontext.h + platform/mac/coregraphicsdevicecontext.mm platform/mac/macclipboard.h platform/mac/macclipboard.mm platform/mac/macfactory.h @@ -232,16 +246,17 @@ set(${target}_mac_sources ########################################################################################## set(${target}_win32_sources + platform/win32/direct2d/d2d.h platform/win32/direct2d/d2dbitmap.cpp platform/win32/direct2d/d2dbitmap.h platform/win32/direct2d/d2dbitmapcache.cpp platform/win32/direct2d/d2dbitmapcache.h - platform/win32/direct2d/d2ddrawcontext.cpp - platform/win32/direct2d/d2ddrawcontext.h platform/win32/direct2d/d2dfont.cpp platform/win32/direct2d/d2dfont.h platform/win32/direct2d/d2dgradient.cpp platform/win32/direct2d/d2dgradient.h + platform/win32/direct2d/d2dgraphicscontext.cpp + platform/win32/direct2d/d2dgraphicscontext.h platform/win32/direct2d/d2dgraphicspath.cpp platform/win32/direct2d/d2dgraphicspath.h platform/win32/win32bitmapbase.h @@ -280,12 +295,12 @@ set(${target}_win32_sources set(${target}_linux_sources platform/linux/cairobitmap.cpp platform/linux/cairobitmap.h - platform/linux/cairocontext.cpp - platform/linux/cairocontext.h platform/linux/cairofont.cpp platform/linux/cairofont.h platform/linux/cairogradient.cpp platform/linux/cairogradient.h + platform/linux/cairographicscontext.cpp + platform/linux/cairographicscontext.h platform/linux/cairopath.cpp platform/linux/cairopath.h platform/linux/cairoutils.h diff --git a/vstgui/lib/cdatabrowser.cpp b/vstgui/lib/cdatabrowser.cpp index ab1d246a3..4e771d238 100644 --- a/vstgui/lib/cdatabrowser.cpp +++ b/vstgui/lib/cdatabrowser.cpp @@ -9,6 +9,7 @@ #include "controls/cscrollbar.h" #include "ifocusdrawing.h" #include "cgraphicspath.h" +#include "cdrawcontext.h" #include "idatabrowserdelegate.h" #include #include diff --git a/vstgui/lib/cdrawcontext.cpp b/vstgui/lib/cdrawcontext.cpp index 61928608b..2605649d2 100644 --- a/vstgui/lib/cdrawcontext.cpp +++ b/vstgui/lib/cdrawcontext.cpp @@ -4,27 +4,81 @@ #include "cdrawcontext.h" #include "cgraphicspath.h" +#include "cgradient.h" #include "cbitmap.h" #include "cstring.h" +#include "platform/iplatformgraphicsdevice.h" #include "platform/iplatformfont.h" +#include "platform/iplatformgraphicspath.h" +#include "platform/iplatformgradient.h" +#include "platform/platformfactory.h" #include +#include namespace VSTGUI { //----------------------------------------------------------------------------- -CDrawContext::CDrawContextState::CDrawContextState (const CDrawContextState& state) +CDrawContext::Transform::Transform (CDrawContext& context, const CGraphicsTransform& transformation) +: context (context) +, transformation (transformation) { - *this = state; + if (transformation.isInvariant () == false) + context.pushTransform (transformation); } //----------------------------------------------------------------------------- -CDrawContext::CDrawContextState::CDrawContextState (CDrawContextState&& state) noexcept +CDrawContext::Transform::~Transform () noexcept { - *this = std::move (state); + if (transformation.isInvariant () == false) + context.popTransform (); } //----------------------------------------------------------------------------- -CDrawContext::CDrawContextState& CDrawContext::CDrawContextState::operator= (CDrawContextState&& state) noexcept +struct CDrawContext::Impl +{ + //----------------------------------------------------------------------------- + struct State + { + SharedPointer font; + CColor frameColor {kTransparentCColor}; + CColor fillColor {kTransparentCColor}; + CColor fontColor {kTransparentCColor}; + CCoord frameWidth {0.}; + CPoint penLoc {}; + CRect clipRect {}; + CLineStyle lineStyle {kLineOnOffDash}; + CDrawMode drawMode {kAntiAliasing}; + float globalAlpha {1.f}; + BitmapInterpolationQuality bitmapQuality {BitmapInterpolationQuality::kDefault}; + + State () = default; + State (const State& state); + State& operator= (const State& state) = default; + State (State&& state) noexcept; + State& operator= (State&& state) noexcept; + }; + + UTF8String* drawStringHelper {nullptr}; + CRect surfaceRect; + double scaleFactor {1.}; + + State currentState; + + std::stack globalStatesStack; + std::stack transformStack; + + PlatformGraphicsDeviceContextPtr device; +}; + +//----------------------------------------------------------------------------- +CDrawContext::Impl::State::State (const State& state) { *this = state; } + +//----------------------------------------------------------------------------- +CDrawContext::Impl::State::State (State&& state) noexcept { *this = std::move (state); } + +//----------------------------------------------------------------------------- +CDrawContext::Impl::State& + CDrawContext::Impl::State::operator= (CDrawContext::Impl::State&& state) noexcept { font = std::move (state.font); frameColor = std::move (state.frameColor); @@ -40,39 +94,47 @@ CDrawContext::CDrawContextState& CDrawContext::CDrawContextState::operator= (CDr } //----------------------------------------------------------------------------- -CDrawContext::Transform::Transform (CDrawContext& context, const CGraphicsTransform& transformation) -: context (context) -, transformation (transformation) +CDrawContext::CDrawContext (const CRect& surfaceRect) { - if (transformation.isInvariant () == false) - context.pushTransform (transformation); -} + impl = std::make_unique (); + impl->surfaceRect = surfaceRect; -//----------------------------------------------------------------------------- -CDrawContext::Transform::~Transform () noexcept -{ - if (transformation.isInvariant () == false) - context.popTransform (); + impl->transformStack.push (CGraphicsTransform ()); } -//----------------------------------------------------------------------------- -CDrawContext::CDrawContext (const CRect& surfaceRect) -: surfaceRect (surfaceRect) +//------------------------------------------------------------------------ +CDrawContext::CDrawContext (const PlatformGraphicsDeviceContextPtr device, const CRect& surfaceRect, + double scaleFactor) +: CDrawContext (surfaceRect) { - transformStack.push (CGraphicsTransform ()); + impl->device = device; + impl->scaleFactor = scaleFactor; + setClipRect (surfaceRect); } //----------------------------------------------------------------------------- CDrawContext::~CDrawContext () noexcept { #if DEBUG - if (!globalStatesStack.empty ()) + if (!impl->globalStatesStack.empty ()) DebugPrint ("Global state stack not empty. Save and restore global state must be called in sequence !\n"); #endif - if (drawStringHelper) - delete drawStringHelper; + if (impl->drawStringHelper) + delete impl->drawStringHelper; +} + +//------------------------------------------------------------------------ +const PlatformGraphicsDeviceContextPtr& CDrawContext::getPlatformDeviceContext () const +{ + return impl->device; } +//----------------------------------------------------------------------------- +const CRect& CDrawContext::getSurfaceRect () const { return impl->surfaceRect; } + +//------------------------------------------------------------------------ +double CDrawContext::getScaleFactor () const { return impl->scaleFactor; } + //----------------------------------------------------------------------------- void CDrawContext::init () { @@ -84,22 +146,26 @@ void CDrawContext::init () setFontColor (kWhiteCColor); setFont (kSystemFont); setDrawMode (kAliasing); - setClipRect (surfaceRect); + setClipRect (impl->surfaceRect); } //----------------------------------------------------------------------------- void CDrawContext::saveGlobalState () { - globalStatesStack.push (currentState); + impl->globalStatesStack.push (impl->currentState); + if (impl->device) + impl->device->saveGlobalState (); } //----------------------------------------------------------------------------- void CDrawContext::restoreGlobalState () { - if (!globalStatesStack.empty ()) + if (impl->device) + impl->device->restoreGlobalState (); + if (!impl->globalStatesStack.empty ()) { - currentState = std::move (globalStatesStack.top ()); - globalStatesStack.pop (); + impl->currentState = std::move (impl->globalStatesStack.top ()); + impl->globalStatesStack.pop (); } else { @@ -110,69 +176,107 @@ void CDrawContext::restoreGlobalState () } //----------------------------------------------------------------------------- -void CDrawContext::setBitmapInterpolationQuality(BitmapInterpolationQuality quality) +void CDrawContext::setBitmapInterpolationQuality (BitmapInterpolationQuality quality) +{ + impl->currentState.bitmapQuality = quality; +} + +//----------------------------------------------------------------------------- +BitmapInterpolationQuality CDrawContext::getBitmapInterpolationQuality () const { - currentState.bitmapQuality = quality; + return impl->currentState.bitmapQuality; } //----------------------------------------------------------------------------- void CDrawContext::setLineStyle (const CLineStyle& style) { - currentState.lineStyle = style; + if (impl->device) + impl->device->setLineStyle (style); + impl->currentState.lineStyle = style; } +//----------------------------------------------------------------------------- +const CLineStyle& CDrawContext::getLineStyle () const { return impl->currentState.lineStyle; } + //----------------------------------------------------------------------------- void CDrawContext::setLineWidth (CCoord width) { - currentState.frameWidth = width; + if (impl->device) + impl->device->setLineWidth (width); + impl->currentState.frameWidth = width; } +//----------------------------------------------------------------------------- +CCoord CDrawContext::getLineWidth () const { return impl->currentState.frameWidth; } + //----------------------------------------------------------------------------- void CDrawContext::setDrawMode (CDrawMode mode) { - currentState.drawMode = mode; + if (impl->device) + impl->device->setDrawMode (mode); + impl->currentState.drawMode = mode; } +//----------------------------------------------------------------------------- +CDrawMode CDrawContext::getDrawMode () const { return impl->currentState.drawMode; } + //----------------------------------------------------------------------------- CRect& CDrawContext::getClipRect (CRect &clip) const { - clip = currentState.clipRect; + clip = impl->currentState.clipRect; getCurrentTransform ().inverse ().transform (clip); clip.normalize (); return clip; } +//----------------------------------------------------------------------------- +const CRect& CDrawContext::getAbsoluteClipRect () const { return impl->currentState.clipRect; } + //----------------------------------------------------------------------------- void CDrawContext::setClipRect (const CRect &clip) { - currentState.clipRect = clip; - getCurrentTransform ().transform (currentState.clipRect); - currentState.clipRect.normalize (); + impl->currentState.clipRect = clip; + getCurrentTransform ().transform (impl->currentState.clipRect); + impl->currentState.clipRect.normalize (); + if (impl->device) + impl->device->setClipRect (impl->currentState.clipRect); } //----------------------------------------------------------------------------- void CDrawContext::resetClipRect () { - currentState.clipRect = surfaceRect; + if (impl->device) + impl->device->setClipRect (getSurfaceRect ()); + impl->currentState.clipRect = getSurfaceRect (); } //----------------------------------------------------------------------------- void CDrawContext::setFillColor (const CColor& color) { - currentState.fillColor = color; + if (impl->device) + impl->device->setFillColor (color); + impl->currentState.fillColor = color; } +//----------------------------------------------------------------------------- +CColor CDrawContext::getFillColor () const { return impl->currentState.fillColor; } + //----------------------------------------------------------------------------- void CDrawContext::setFrameColor (const CColor& color) { - currentState.frameColor = color; + if (impl->device) + impl->device->setFrameColor (color); + impl->currentState.frameColor = color; } //----------------------------------------------------------------------------- -void CDrawContext::setFontColor (const CColor& color) -{ - currentState.fontColor = color; -} +CColor CDrawContext::getFrameColor () const { return impl->currentState.frameColor; } + +//----------------------------------------------------------------------------- +void CDrawContext::setFontColor (const CColor& color) { impl->currentState.fontColor = color; } + +//----------------------------------------------------------------------------- +CColor CDrawContext::getFontColor () const { return impl->currentState.fontColor; } //----------------------------------------------------------------------------- void CDrawContext::setFont (const CFontRef newFont, const CCoord& size, const int32_t& style) @@ -181,94 +285,103 @@ void CDrawContext::setFont (const CFontRef newFont, const CCoord& size, const in return; if ((size > 0 && newFont->getSize () != size) || (style != -1 && newFont->getStyle () != style)) { - currentState.font = makeOwned (*newFont); + impl->currentState.font = makeOwned (*newFont); if (size > 0) - currentState.font->setSize (size); + impl->currentState.font->setSize (size); if (style != -1) - currentState.font->setStyle (style); + impl->currentState.font->setStyle (style); } else { - currentState.font = newFont; + impl->currentState.font = newFont; } } +//----------------------------------------------------------------------------- +const CFontRef CDrawContext::getFont () const { return impl->currentState.font; } + //----------------------------------------------------------------------------- void CDrawContext::setGlobalAlpha (float newAlpha) { - currentState.globalAlpha = newAlpha; + if (impl->device) + impl->device->setGlobalAlpha (newAlpha); + impl->currentState.globalAlpha = newAlpha; } +//----------------------------------------------------------------------------- +float CDrawContext::getGlobalAlpha () const { return impl->currentState.globalAlpha; } + //----------------------------------------------------------------------------- const UTF8String& CDrawContext::getDrawString (UTF8StringPtr string) { - if (drawStringHelper == nullptr) - drawStringHelper = new UTF8String (string); + if (impl->drawStringHelper == nullptr) + impl->drawStringHelper = new UTF8String (string); else - drawStringHelper->assign (string); - return *drawStringHelper; + impl->drawStringHelper->assign (string); + return *impl->drawStringHelper; } //----------------------------------------------------------------------------- void CDrawContext::clearDrawString () { - if (drawStringHelper) - drawStringHelper->clear (); + if (impl->drawStringHelper) + impl->drawStringHelper->clear (); } //------------------------------------------------------------------------ CCoord CDrawContext::getStringWidth (IPlatformString* string) { CCoord result = -1; - if (currentState.font == nullptr || string == nullptr) + if (impl->currentState.font == nullptr || string == nullptr) return result; - - if (auto painter = currentState.font->getFontPainter ()) - result = painter->getStringWidth (this, string, true); - + + if (auto painter = impl->currentState.font->getFontPainter ()) + result = painter->getStringWidth (impl->device, string, true); + return result; } //------------------------------------------------------------------------ void CDrawContext::drawString (IPlatformString* string, const CRect& _rect, const CHoriTxtAlign hAlign, bool antialias) { - if (!string || currentState.font == nullptr) + if (!string || impl->currentState.font == nullptr) return; - auto painter = currentState.font->getFontPainter (); + auto painter = impl->currentState.font->getFontPainter (); if (painter == nullptr) return; CRect rect (_rect); double capHeight = -1; - auto platformFont = currentState.font->getPlatformFont (); + auto platformFont = impl->currentState.font->getPlatformFont (); if (platformFont) capHeight = platformFont->getCapHeight (); if (capHeight > 0.) rect.bottom -= (rect.getHeight () / 2. - capHeight / 2.); else - rect.bottom -= (rect.getHeight () / 2. - currentState.font->getSize () / 2.) + 1.; + rect.bottom -= (rect.getHeight () / 2. - impl->currentState.font->getSize () / 2.) + 1.; if (hAlign != kLeftText) { - CCoord stringWidth = painter->getStringWidth (this, string, antialias); + CCoord stringWidth = painter->getStringWidth (impl->device, string, antialias); if (hAlign == kRightText) rect.left = rect.right - stringWidth; else rect.left = rect.left + (rect.getWidth () / 2.) - (stringWidth / 2.); } - painter->drawString (this, string, CPoint (rect.left, rect.bottom), antialias); + painter->drawString (impl->device, string, CPoint (rect.left, rect.bottom), + impl->currentState.fontColor, antialias); } //------------------------------------------------------------------------ void CDrawContext::drawString (IPlatformString* string, const CPoint& point, bool antialias) { - if (string == nullptr || currentState.font == nullptr) + if (string == nullptr || impl->currentState.font == nullptr) return; - - if (auto painter = currentState.font->getFontPainter ()) - painter->drawString (this, string, point, antialias); + + if (auto painter = impl->currentState.font->getFontPainter ()) + painter->drawString (impl->device, string, point, impl->currentState.fontColor, antialias); } //----------------------------------------------------------------------------- @@ -296,6 +409,31 @@ void CDrawContext::fillRectWithBitmap (CBitmap* bitmap, const CRect& srcRect, co { if (srcRect.isEmpty () || dstRect.isEmpty ()) return; + if (srcRect.getWidth () == dstRect.getWidth () && srcRect.getHeight () == dstRect.getHeight ()) + { + drawBitmap (bitmap, dstRect, srcRect.getTopLeft (), alpha); + return; + } + + if (impl->device) + { + if (auto deviceBitmapExt = impl->device->asBitmapExt ()) + { + double transformedScaleFactor = getScaleFactor (); + CGraphicsTransform t = getCurrentTransform (); + if (t.m11 == t.m22 && t.m12 == 0 && t.m21 == 0) + transformedScaleFactor *= t.m11; + + if (auto pb = bitmap->getBestPlatformBitmapForScaleFactor (transformedScaleFactor)) + { + if (deviceBitmapExt->fillRectWithBitmap (*pb, srcRect, dstRect, alpha, + getBitmapInterpolationQuality ())) + { + return; + } + } + } + } CRect bitmapPartRect; CPoint sourceOffset (srcRect.left, srcRect.top); @@ -328,6 +466,26 @@ void CDrawContext::fillRectWithBitmap (CBitmap* bitmap, const CRect& srcRect, co //----------------------------------------------------------------------------- void CDrawContext::drawBitmapNinePartTiled (CBitmap* bitmap, const CRect& dest, const CNinePartTiledDescription& desc, float alpha) { + if (impl->device) + { + if (auto deviceBitmapExt = impl->device->asBitmapExt ()) + { + double transformedScaleFactor = getScaleFactor (); + CGraphicsTransform t = getCurrentTransform (); + if (t.m11 == t.m22 && t.m12 == 0 && t.m21 == 0) + transformedScaleFactor *= t.m11; + + if (auto pb = bitmap->getBestPlatformBitmapForScaleFactor (transformedScaleFactor)) + { + if (deviceBitmapExt->drawBitmapNinePartTiled (*pb, dest, desc, alpha, + getBitmapInterpolationQuality ())) + { + return; + } + } + } + } + CRect myBitmapBounds (0, 0, bitmap->getWidth (), bitmap->getHeight ()); CRect mySourceRect [CNinePartTiledDescription::kPartCount]; CRect myDestRect [CNinePartTiledDescription::kPartCount]; @@ -342,34 +500,38 @@ void CDrawContext::drawBitmapNinePartTiled (CBitmap* bitmap, const CRect& dest, //----------------------------------------------------------------------------- CGraphicsPath* CDrawContext::createRoundRectGraphicsPath (const CRect& size, CCoord radius) { - CGraphicsPath* path = createGraphicsPath (); - if (path) + if (auto path = createGraphicsPath ()) { path->addRoundRect (size, radius); + return path; } - return path; + return {}; } //----------------------------------------------------------------------------- void CDrawContext::pushTransform (const CGraphicsTransform& transformation) { - vstgui_assert (!transformStack.empty ()); - const CGraphicsTransform& currentTransform = transformStack.top (); + vstgui_assert (!impl->transformStack.empty ()); + const CGraphicsTransform& currentTransform = impl->transformStack.top (); CGraphicsTransform newTransform = currentTransform * transformation; - transformStack.push (newTransform); + impl->transformStack.push (newTransform); + if (impl->device) + impl->device->setTransformMatrix (newTransform); } //----------------------------------------------------------------------------- void CDrawContext::popTransform () { - vstgui_assert (transformStack.size () > 1); - transformStack.pop (); + vstgui_assert (impl->transformStack.size () > 1); + impl->transformStack.pop (); + if (impl->device) + impl->device->setTransformMatrix (impl->transformStack.top ()); } //----------------------------------------------------------------------------- const CGraphicsTransform& CDrawContext::getCurrentTransform () const { - return transformStack.top (); + return impl->transformStack.top (); } //------------------------------------------------------------------------ @@ -378,4 +540,212 @@ CCoord CDrawContext::getHairlineSize () const return 1. / (getScaleFactor () * getCurrentTransform ().m11); } +//------------------------------------------------------------------------ +static PlatformGraphicsDrawStyle convert (CDrawStyle s) +{ + switch (s) + { + case CDrawStyle::kDrawFilled: + return PlatformGraphicsDrawStyle::Filled; + case CDrawStyle::kDrawStroked: + return PlatformGraphicsDrawStyle::Stroked; + case CDrawStyle::kDrawFilledAndStroked: + return PlatformGraphicsDrawStyle::FilledAndStroked; + default: + assert (false); + } + return {}; +} + +//------------------------------------------------------------------------ +void CDrawContext::drawLine (const LinePair& line) +{ + if (impl->device) + impl->device->drawLine (line); +} + +//------------------------------------------------------------------------ +void CDrawContext::drawLines (const LineList& lines) +{ + if (impl->device) + impl->device->drawLines (lines); +} + +//------------------------------------------------------------------------ +void CDrawContext::drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle) +{ + if (impl->device) + impl->device->drawPolygon (polygonPointList, convert (drawStyle)); +} + +//------------------------------------------------------------------------ +void CDrawContext::drawRect (const CRect& rect, const CDrawStyle drawStyle) +{ + if (impl->device) + impl->device->drawRect (rect, convert (drawStyle)); +} + +//------------------------------------------------------------------------ +void CDrawContext::drawArc (const CRect& rect, const float startAngle1, const float endAngle2, + const CDrawStyle drawStyle) +{ + if (impl->device) + impl->device->drawArc (rect, startAngle1, endAngle2, convert (drawStyle)); +} + +//------------------------------------------------------------------------ +void CDrawContext::drawEllipse (const CRect& rect, const CDrawStyle drawStyle) +{ + if (impl->device) + impl->device->drawEllipse (rect, convert (drawStyle)); +} + +//------------------------------------------------------------------------ +void CDrawContext::drawPoint (const CPoint& point, const CColor& color) +{ + if (impl->device && impl->device->drawPoint (point, color)) + return; + + // if the platform does not support drawing points, emulate it somehow + + saveGlobalState (); + + CRect r (point.x, point.y, point.x, point.y); + r.inset (-0.5, -0.5); + setDrawMode (kAliasing); + setFillColor (color); + drawRect (r, kDrawFilled); + + restoreGlobalState (); +} + +//------------------------------------------------------------------------ +void CDrawContext::drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset, + float alpha) +{ + if (impl->device) + { + double transformedScaleFactor = getScaleFactor (); + CGraphicsTransform t = getCurrentTransform (); + if (t.m11 == t.m22 && t.m12 == 0 && t.m21 == 0) + transformedScaleFactor *= t.m11; + if (auto pb = bitmap->getBestPlatformBitmapForScaleFactor (transformedScaleFactor)) + impl->device->drawBitmap (*pb, dest, offset, alpha, getBitmapInterpolationQuality ()); + } +} + +//------------------------------------------------------------------------ +void CDrawContext::clearRect (const CRect& rect) +{ + if (impl->device) + impl->device->clearRect (rect); +} + +//------------------------------------------------------------------------ +static PlatformGraphicsPathDrawMode convert (CDrawContext::PathDrawMode mode) +{ + switch (mode) + { + case CDrawContext::PathDrawMode::kPathFilled: + return PlatformGraphicsPathDrawMode::Filled; + case CDrawContext::PathDrawMode::kPathStroked: + return PlatformGraphicsPathDrawMode::Stroked; + case CDrawContext::PathDrawMode::kPathFilledEvenOdd: + return PlatformGraphicsPathDrawMode::FilledEvenOdd; + default: + assert (false); + } + return {}; +} + +//------------------------------------------------------------------------ +void CDrawContext::drawGraphicsPath (CGraphicsPath* path, PathDrawMode mode, + CGraphicsTransform* transformation) +{ + if (impl->device) + { + if (auto& pp = path->getPlatformPath (mode == kPathFilledEvenOdd + ? PlatformGraphicsPathFillMode::Alternate + : PlatformGraphicsPathFillMode::Winding)) + impl->device->drawGraphicsPath (*pp.get (), convert (mode), transformation); + } +} + +//------------------------------------------------------------------------ +void CDrawContext::fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, + const CPoint& startPoint, const CPoint& endPoint, + bool evenOdd, CGraphicsTransform* transformation) +{ + if (impl->device) + { + if (auto& platformGradient = gradient.getPlatformGradient ()) + { + if (auto& pp = path->getPlatformPath (evenOdd ? PlatformGraphicsPathFillMode::Alternate + : PlatformGraphicsPathFillMode::Winding)) + { + impl->device->fillLinearGradient (*pp.get (), *platformGradient, startPoint, + endPoint, evenOdd, transformation); + } + } + } +} + +//------------------------------------------------------------------------ +void CDrawContext::fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, + const CPoint& center, CCoord radius, + const CPoint& originOffset, bool evenOdd, + CGraphicsTransform* transformation) +{ + if (impl->device) + { + if (auto& platformGradient = gradient.getPlatformGradient ()) + { + if (auto& pp = path->getPlatformPath (evenOdd ? PlatformGraphicsPathFillMode::Alternate + : PlatformGraphicsPathFillMode::Winding)) + { + impl->device->fillRadialGradient (*pp.get (), *platformGradient, center, radius, + originOffset, evenOdd, transformation); + } + } + } +} + +//------------------------------------------------------------------------ +CGraphicsPath* CDrawContext::createGraphicsPath () +{ + if (impl->device) + return new CGraphicsPath (impl->device->getGraphicsPathFactory ()); + return nullptr; +} + +//------------------------------------------------------------------------ +CGraphicsPath* CDrawContext::createTextPath (const CFontRef font, UTF8StringPtr text) +{ + if (impl->device) + { + auto platformFont = font->getPlatformFont (); + auto pathFactory = impl->device->getGraphicsPathFactory (); + if (platformFont && pathFactory) + { + if (auto textPath = pathFactory->createTextPath (platformFont, text)) + return new CGraphicsPath (pathFactory, std::move (textPath)); + } + } + return nullptr; +} + +//------------------------------------------------------------------------ +void CDrawContext::beginDraw () +{ + if (impl->device) + impl->device->beginDraw (); +} + +//------------------------------------------------------------------------ +void CDrawContext::endDraw () +{ + if (impl->device) + impl->device->endDraw (); +} + } // VSTGUI diff --git a/vstgui/lib/cdrawcontext.h b/vstgui/lib/cdrawcontext.h index 697b8f37a..6aa80f83e 100644 --- a/vstgui/lib/cdrawcontext.h +++ b/vstgui/lib/cdrawcontext.h @@ -14,7 +14,6 @@ #include "clinestyle.h" #include "cdrawdefs.h" #include -#include #include namespace VSTGUI { @@ -45,41 +44,49 @@ class CDrawContext : public AtomicReferenceCounted /// @name Draw primitives //----------------------------------------------------------------------------- //@{ - using LinePair = std::pair; - using LineList = std::vector; - using PointList = std::vector; + using LinePair = VSTGUI::LinePair; + using LineList = VSTGUI::LineList; + using PointList = VSTGUI::PointList; - inline void drawLine (const CPoint& start, const CPoint& end) { drawLine (std::make_pair (start, end)); } + inline void drawLine (const CPoint& start, const CPoint& end) + { + drawLine (std::make_pair (start, end)); + } /** draw a line */ - virtual void drawLine (const LinePair& line) = 0; + void drawLine (const LinePair& line); /** draw multiple lines at once */ - virtual void drawLines (const LineList& lines) = 0; + void drawLines (const LineList& lines); /** draw a polygon */ - virtual void drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle = kDrawStroked) = 0; + void drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle = kDrawStroked); /** draw a rect */ - virtual void drawRect (const CRect &rect, const CDrawStyle drawStyle = kDrawStroked) = 0; + void drawRect (const CRect& rect, const CDrawStyle drawStyle = kDrawStroked); /** draw an arc, angles are in degree */ - virtual void drawArc (const CRect &rect, const float startAngle1, const float endAngle2, const CDrawStyle drawStyle = kDrawStroked) = 0; + void drawArc (const CRect& rect, const float startAngle1, const float endAngle2, + const CDrawStyle drawStyle = kDrawStroked); /** draw an ellipse */ - virtual void drawEllipse (const CRect &rect, const CDrawStyle drawStyle = kDrawStroked) = 0; + void drawEllipse (const CRect& rect, const CDrawStyle drawStyle = kDrawStroked); /** draw a point */ - virtual void drawPoint (const CPoint &point, const CColor& color) = 0; + void drawPoint (const CPoint& point, const CColor& color); /** don't call directly, please use CBitmap::draw instead */ - virtual void drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset = CPoint (0, 0), float alpha = 1.f) = 0; - virtual void drawBitmapNinePartTiled (CBitmap* bitmap, const CRect& dest, const CNinePartTiledDescription& desc, float alpha = 1.f); - virtual void fillRectWithBitmap (CBitmap* bitmap, const CRect& srcRect, const CRect& dstRect, float alpha); + void drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset = CPoint (0, 0), + float alpha = 1.f); + void drawBitmapNinePartTiled (CBitmap* bitmap, const CRect& dest, + const CNinePartTiledDescription& desc, float alpha = 1.f); + void fillRectWithBitmap (CBitmap* bitmap, const CRect& srcRect, const CRect& dstRect, + float alpha); /** clears the rect (makes r = 0, g = 0, b = 0, a = 0) */ - virtual void clearRect (const CRect& rect) = 0; + void clearRect (const CRect& rect); //@} //----------------------------------------------------------------------------- // @name Bitmap Interpolation Quality //----------------------------------------------------------------------------- //@{ - virtual void setBitmapInterpolationQuality (BitmapInterpolationQuality quality); ///< set the current bitmap interpolation quality - const BitmapInterpolationQuality& getBitmapInterpolationQuality () const { return currentState.bitmapQuality; } ///< get the current bitmap interpolation quality - + /** set the current bitmap interpolation quality */ + void setBitmapInterpolationQuality (BitmapInterpolationQuality quality); + /** get the current bitmap interpolation quality */ + BitmapInterpolationQuality getBitmapInterpolationQuality () const; //@} //----------------------------------------------------------------------------- @@ -87,14 +94,14 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ /** set the current line style */ - virtual void setLineStyle (const CLineStyle& style); + void setLineStyle (const CLineStyle& style); /** get the current line style */ - const CLineStyle& getLineStyle () const { return currentState.lineStyle; } + const CLineStyle& getLineStyle () const; /** set the current line width */ - virtual void setLineWidth (CCoord width); + void setLineWidth (CCoord width); /** get the current line width */ - CCoord getLineWidth () const { return currentState.frameWidth; } + CCoord getLineWidth () const; //@} //----------------------------------------------------------------------------- @@ -102,9 +109,9 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ /** set the current draw mode, see CDrawMode */ - virtual void setDrawMode (CDrawMode mode); + void setDrawMode (CDrawMode mode); /** get the current draw mode, see CDrawMode */ - CDrawMode getDrawMode () const { return currentState.drawMode; } + CDrawMode getDrawMode () const; //@} //----------------------------------------------------------------------------- @@ -112,11 +119,11 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ /** set the current clip */ - virtual void setClipRect (const CRect &clip); + void setClipRect (const CRect& clip); /** get the current clip */ CRect& getClipRect (CRect &clip) const; /** reset the clip to the default state */ - virtual void resetClipRect (); + void resetClipRect (); //@} //----------------------------------------------------------------------------- @@ -124,13 +131,13 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ /** set current fill color */ - virtual void setFillColor (const CColor& color); + void setFillColor (const CColor& color); /** get current fill color */ - CColor getFillColor () const { return currentState.fillColor; } + CColor getFillColor () const; /** set current stroke color */ - virtual void setFrameColor (const CColor& color); + void setFrameColor (const CColor& color); /** get current stroke color */ - CColor getFrameColor () const { return currentState.frameColor; } + CColor getFrameColor () const; //@} //----------------------------------------------------------------------------- @@ -138,13 +145,13 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ /** set current font color */ - virtual void setFontColor (const CColor& color); + void setFontColor (const CColor& color); /** get current font color */ - CColor getFontColor () const { return currentState.fontColor; } + CColor getFontColor () const; /** set current font */ - virtual void setFont (const CFontRef font, const CCoord& size = 0, const int32_t& style = -1); + void setFont (const CFontRef font, const CCoord& size = 0, const int32_t& style = -1); /** get current font */ - const CFontRef getFont () const { return currentState.font; } + const CFontRef getFont () const; //@} //----------------------------------------------------------------------------- @@ -154,14 +161,16 @@ class CDrawContext : public AtomicReferenceCounted /** get the width of an UTF-8 encoded string */ CCoord getStringWidth (UTF8StringPtr pStr); /** draw an UTF-8 encoded string */ - void drawString (UTF8StringPtr string, const CRect& _rect, const CHoriTxtAlign hAlign = kCenterText, bool antialias = true); + void drawString (UTF8StringPtr string, const CRect& _rect, + const CHoriTxtAlign hAlign = kCenterText, bool antialias = true); /** draw an UTF-8 encoded string */ void drawString (UTF8StringPtr string, const CPoint& _point, bool antialias = true); /** get the width of a platform string */ CCoord getStringWidth (IPlatformString* pStr); /** draw a platform string */ - void drawString (IPlatformString* string, const CRect& _rect, const CHoriTxtAlign hAlign = kCenterText, bool antialias = true); + void drawString (IPlatformString* string, const CRect& _rect, + const CHoriTxtAlign hAlign = kCenterText, bool antialias = true); /** draw a platform string */ void drawString (IPlatformString* string, const CPoint& _point, bool antialias = true); //@} @@ -171,17 +180,17 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ /** sets the global alpha value[0..1] */ - virtual void setGlobalAlpha (float newAlpha); + void setGlobalAlpha (float newAlpha); /** get current global alpha value */ - float getGlobalAlpha () const { return currentState.globalAlpha; } + float getGlobalAlpha () const; //@} //----------------------------------------------------------------------------- /// @name Global State Stack //----------------------------------------------------------------------------- //@{ - virtual void saveGlobalState (); - virtual void restoreGlobalState (); + void saveGlobalState (); + void restoreGlobalState (); //@} //----------------------------------------------------------------------------- @@ -189,11 +198,11 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ const CGraphicsTransform& getCurrentTransform () const; - const CRect& getAbsoluteClipRect () const { return currentState.clipRect; } + const CRect& getAbsoluteClipRect () const; /** returns the backend scale factor. */ - virtual double getScaleFactor () const { return 1.; } - + double getScaleFactor () const; + /** returns the current line size which corresponds to one pixel on screen. * * do not cache this value, instead ask for it every time you need it. @@ -206,9 +215,9 @@ class CDrawContext : public AtomicReferenceCounted //----------------------------------------------------------------------------- //@{ /** create a graphics path object, you need to forget it after usage */ - virtual CGraphicsPath* createGraphicsPath () = 0; + CGraphicsPath* createGraphicsPath (); /** create a graphics path from a text */ - virtual CGraphicsPath* createTextPath (const CFontRef font, UTF8StringPtr text) = 0; + CGraphicsPath* createTextPath (const CFontRef font, UTF8StringPtr text); /** create a rect with round corners as graphics path, you need to forget it after usage */ CGraphicsPath* createRoundRectGraphicsPath (const CRect& size, CCoord radius); @@ -220,22 +229,32 @@ class CDrawContext : public AtomicReferenceCounted kPathStroked }; - virtual void drawGraphicsPath (CGraphicsPath* path, PathDrawMode mode = kPathFilled, CGraphicsTransform* transformation = nullptr) = 0; - virtual void fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& startPoint, const CPoint& endPoint, bool evenOdd = false, CGraphicsTransform* transformation = nullptr) = 0; - virtual void fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& center, CCoord radius, const CPoint& originOffset = CPoint (0,0), bool evenOdd = false, CGraphicsTransform* transformation = nullptr) = 0; + void drawGraphicsPath (CGraphicsPath* path, PathDrawMode mode = kPathFilled, + CGraphicsTransform* transformation = nullptr); + void fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, + const CPoint& startPoint, const CPoint& endPoint, bool evenOdd = false, + CGraphicsTransform* transformation = nullptr); + void fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& center, + CCoord radius, const CPoint& originOffset = CPoint (0, 0), + bool evenOdd = false, CGraphicsTransform* transformation = nullptr); //@} - virtual void beginDraw () {} - virtual void endDraw () {} + void beginDraw (); + void endDraw (); + + const CRect& getSurfaceRect () const; + + CDrawContext (const PlatformGraphicsDeviceContextPtr device, const CRect& surfaceRect, + double scaleFactor); + ~CDrawContext () noexcept override; - const CRect& getSurfaceRect () const { return surfaceRect; } + const PlatformGraphicsDeviceContextPtr& getPlatformDeviceContext () const; protected: CDrawContext () = delete; explicit CDrawContext (const CRect& surfaceRect); - ~CDrawContext () noexcept override; - virtual void init (); + void init (); void pushTransform (const CGraphicsTransform& transformation); void popTransform (); @@ -243,40 +262,9 @@ class CDrawContext : public AtomicReferenceCounted const UTF8String& getDrawString (UTF8StringPtr string); void clearDrawString (); - /// @cond ignore - struct CDrawContextState - { - SharedPointer font; - CColor frameColor {kTransparentCColor}; - CColor fillColor {kTransparentCColor}; - CColor fontColor {kTransparentCColor}; - CCoord frameWidth {0.}; - CPoint penLoc {}; - CRect clipRect {}; - CLineStyle lineStyle {kLineOnOffDash}; - CDrawMode drawMode {kAntiAliasing}; - float globalAlpha {1.f}; - BitmapInterpolationQuality bitmapQuality {BitmapInterpolationQuality::kDefault}; - - CDrawContextState () = default; - CDrawContextState (const CDrawContextState& state); - CDrawContextState& operator= (const CDrawContextState& state) = default; - CDrawContextState (CDrawContextState&& state) noexcept; - CDrawContextState& operator= (CDrawContextState&& state) noexcept; - }; - /// @endcond - - const CDrawContextState& getCurrentState () const { return currentState; } - CDrawContextState& getCurrentState () { return currentState; } - private: - UTF8String* drawStringHelper {nullptr}; - CRect surfaceRect; - - CDrawContextState currentState; - - std::stack globalStatesStack; - std::stack transformStack; + struct Impl; + std::unique_ptr impl; }; //----------------------------------------------------------------------------- diff --git a/vstgui/lib/cexternalview.cpp b/vstgui/lib/cexternalview.cpp new file mode 100644 index 000000000..ec6631d8f --- /dev/null +++ b/vstgui/lib/cexternalview.cpp @@ -0,0 +1,314 @@ +// 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 "cexternalview.h" +#include "cframe.h" +#include "platform/iplatformframe.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { + +//------------------------------------------------------------------------ +struct CExternalViewBaseImpl +{ +private: + using ExternalViewPtr = std::shared_ptr; + + ExternalViewPtr view; + bool isAttached {false}; + + static ExternalView::IntRect fromCRect (const CRect& r) + { + ExternalView::IntRect res; + res.origin.x = static_cast (std::ceil (r.left)); + res.origin.y = static_cast (std::ceil (r.top)); + res.size.width = static_cast (std::floor (r.getWidth ())); + res.size.height = static_cast (std::floor (r.getHeight ())); + return res; + } + + static CRect calculateSize (CViewContainer* parent, CRect newSize) + { + CFrame* frame = parent ? parent->getFrame () : nullptr; + while (parent && parent != frame) + { + CRect parentSize = parent->getViewSize (); + parent->getTransform ().transform (newSize); + newSize.offset (parentSize.left, parentSize.top); + newSize.bound (parentSize); + parent = static_cast (parent->getParentView ()); + } + if (frame) + frame->getTransform ().transform (newSize); + return newSize; + } + +public: + CExternalViewBaseImpl (const ExternalViewPtr& v) : view (v) {} + + void updateSize (CViewContainer* parent, CRect localSize, CRect globalSize) + { + if (!view) + return; + localSize = calculateSize (parent, localSize); + view->setViewSize (fromCRect (globalSize), fromCRect (localSize)); + } + + void attach (CFrame* frame) + { + if (!frame || !view) + return; + auto platformFrame = frame->getPlatformFrame (); + if (!platformFrame) + return; + ExternalView::PlatformViewType viewType = {}; + switch (platformFrame->getPlatformType ()) + { + case PlatformType::kHWND: + viewType = ExternalView::PlatformViewType::HWND; + break; + case PlatformType::kNSView: + viewType = ExternalView::PlatformViewType::NSView; + break; + default: + return; + } + if (!view->platformViewTypeSupported (viewType)) + return; + isAttached = view->attach (platformFrame->getPlatformRepresentation (), viewType); + } + void remove () + { + if (view && isAttached) + { + view->remove (); + isAttached = false; + } + } + void scaleFactorChanged (double scaleFactor) + { + if (view) + view->setContentScaleFactor (scaleFactor); + } + void takeFocus () + { + if (view) + view->takeFocus (); + } + void looseFocus () + { + if (view) + view->looseFocus (); + } + void enableMouse (bool state) + { + if (view) + view->setMouseEnabled (state); + } + + ExternalView::IView* getView () const { return view.get (); } +}; + +//------------------------------------------------------------------------ +struct CExternalView::Impl : CExternalViewBaseImpl +{ + using CExternalViewBaseImpl::CExternalViewBaseImpl; +}; + +//------------------------------------------------------------------------ +CExternalView::CExternalView (const CRect& r, const ExternalViewPtr& view) : CView (r) +{ + impl = std::make_unique (view); + impl->getView ()->setTookFocusCallback ([this] () { + if (auto frame = getFrame ()) + frame->setFocusView (this); + }); +} + +//------------------------------------------------------------------------ +CExternalView::~CExternalView () noexcept { impl->getView ()->setTookFocusCallback (nullptr); } + +//------------------------------------------------------------------------ +bool CExternalView::attached (CView* parent) +{ + if (CView::attached (parent)) + { + auto frame = parent->getFrame (); + impl->updateSize (parent->asViewContainer (), getViewSize (), + translateToGlobal (getViewSize ())); + impl->scaleFactorChanged (frame->getScaleFactor ()); + impl->attach (frame); + frame->registerScaleFactorChangedListener (this); + return true; + } + return false; +} + +//------------------------------------------------------------------------ +bool CExternalView::removed (CView* parent) +{ + if (auto frame = parent->getFrame ()) + { + frame->unregisterScaleFactorChangedListener (this); + } + impl->remove (); + return CView::removed (parent); +} + +//------------------------------------------------------------------------ +void CExternalView::takeFocus () { impl->takeFocus (); } + +//------------------------------------------------------------------------ +void CExternalView::looseFocus () { impl->looseFocus (); } + +//------------------------------------------------------------------------ +void CExternalView::setViewSize (const CRect& rect, bool invalid) +{ + CView::setViewSize (rect, invalid); + impl->updateSize (getParentView () ? getParentView ()->asViewContainer () : nullptr, + getViewSize (), translateToGlobal (getViewSize ())); +} + +//------------------------------------------------------------------------ +void CExternalView::parentSizeChanged () +{ + impl->updateSize (getParentView () ? getParentView ()->asViewContainer () : nullptr, + getViewSize (), translateToGlobal (getViewSize ())); +} + +//------------------------------------------------------------------------ +void CExternalView::onScaleFactorChanged (CFrame* frame, double newScaleFactor) +{ + impl->scaleFactorChanged (newScaleFactor); +} + +//------------------------------------------------------------------------ +void CExternalView::setMouseEnabled (bool enable) +{ + impl->enableMouse (enable); + CView::setMouseEnabled (enable); +} + +//------------------------------------------------------------------------ +ExternalView::IView* CExternalView::getExternalView () const { return impl->getView (); } + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +struct CExternalControl::Impl : CExternalViewBaseImpl +{ + using CExternalViewBaseImpl::CExternalViewBaseImpl; +}; + +//------------------------------------------------------------------------ +CExternalControl::CExternalControl (const CRect& r, const ExternalControlPtr& view) +: CControl (r, nullptr) +{ + impl = std::make_unique (view); + impl->getView ()->setTookFocusCallback ([this] () { + if (auto frame = getFrame ()) + frame->setFocusView (this); + }); + auto control = dynamic_cast (impl->getView ()); + vstgui_assert ( + control, "Please provide an object that inherits from ExternalView::IControlViewExtension"); + control->setEditCallbacks ({ + [this] () { beginEdit (); }, + [this] (double newValue) { + auto old = getValue (); + setValueNormalized (static_cast (newValue)); + if (old != getValue ()) + valueChanged (); + }, + [this] () { endEdit (); }, + }); + setWantsFocus (true); +} + +//------------------------------------------------------------------------ +CExternalControl::~CExternalControl () noexcept +{ + impl->getView ()->setTookFocusCallback (nullptr); + auto control = dynamic_cast (impl->getView ()); + control->setEditCallbacks ({}); +} + +//------------------------------------------------------------------------ +void CExternalControl::setValue (float val) +{ + CControl::setValue (val); + auto control = dynamic_cast (impl->getView ()); + control->setValue (getValueNormalized ()); +} + +//------------------------------------------------------------------------ +bool CExternalControl::attached (CView* parent) +{ + if (CControl::attached (parent)) + { + auto frame = parent->getFrame (); + impl->updateSize (parent->asViewContainer (), getViewSize (), + translateToGlobal (getViewSize ())); + impl->scaleFactorChanged (frame->getScaleFactor ()); + impl->attach (frame); + frame->registerScaleFactorChangedListener (this); + return true; + } + return false; +} + +//------------------------------------------------------------------------ +bool CExternalControl::removed (CView* parent) +{ + if (auto frame = parent->getFrame ()) + { + frame->unregisterScaleFactorChangedListener (this); + } + impl->remove (); + return CControl::removed (parent); +} + +//------------------------------------------------------------------------ +void CExternalControl::takeFocus () { impl->takeFocus (); } + +//------------------------------------------------------------------------ +void CExternalControl::looseFocus () { impl->looseFocus (); } + +//------------------------------------------------------------------------ +void CExternalControl::setViewSize (const CRect& rect, bool invalid) +{ + CControl::setViewSize (rect, invalid); + impl->updateSize (getParentView () ? getParentView ()->asViewContainer () : nullptr, + getViewSize (), translateToGlobal (getViewSize ())); +} + +//------------------------------------------------------------------------ +void CExternalControl::parentSizeChanged () +{ + impl->updateSize (getParentView () ? getParentView ()->asViewContainer () : nullptr, + getViewSize (), translateToGlobal (getViewSize ())); +} + +//------------------------------------------------------------------------ +void CExternalControl::onScaleFactorChanged (CFrame* frame, double newScaleFactor) +{ + impl->scaleFactorChanged (newScaleFactor); +} + +//------------------------------------------------------------------------ +void CExternalControl::setMouseEnabled (bool enable) +{ + impl->enableMouse (enable); + CControl::setMouseEnabled (enable); +} + +//------------------------------------------------------------------------ +ExternalView::IView* CExternalControl::getExternalView () const { return impl->getView (); } + +//------------------------------------------------------------------------ +bool CExternalControl::getFocusPath (CGraphicsPath& outPath) { return true; } + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/cexternalview.h b/vstgui/lib/cexternalview.h new file mode 100644 index 000000000..42080aedd --- /dev/null +++ b/vstgui/lib/cexternalview.h @@ -0,0 +1,86 @@ +// 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 "cview.h" +#include "controls/ccontrol.h" +#include "iscalefactorchangedlistener.h" +#include "iexternalview.h" +#include + +//------------------------------------------------------------------------ +namespace VSTGUI { + +//------------------------------------------------------------------------ +/** View to embed non CView views into VSTGUI + * + * This view is the umbrella for views from other view systems (like HWND child windows or + * NSViews). + * The actual implementation for the external view must be done via ExternalView::IView + * + * @ingroup new_in_4_13 + */ +class CExternalView : public CView, + public IScaleFactorChangedListener, + public ExternalView::IViewEmbedder +{ +public: + using ExternalViewPtr = std::shared_ptr; + + CExternalView (const CRect& r, const ExternalViewPtr& view); + ~CExternalView () noexcept; + + bool attached (CView* parent) override; + bool removed (CView* parent) override; + void takeFocus () override; + void looseFocus () override; + void setViewSize (const CRect& rect, bool invalid = true) override; + void parentSizeChanged () override; + void onScaleFactorChanged (CFrame* frame, double newScaleFactor) override; + void setMouseEnabled (bool enable = true) override; + + ExternalView::IView* getExternalView () const override; + +private: + + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +class CExternalControl : public CControl, + public IScaleFactorChangedListener, + public ExternalView::IViewEmbedder +{ +public: + using ExternalControlPtr = std::shared_ptr; + + CExternalControl (const CRect& r, const ExternalControlPtr& control); + ~CExternalControl () noexcept; + + void setValue (float val) override; + + bool attached (CView* parent) override; + bool removed (CView* parent) override; + void takeFocus () override; + void looseFocus () override; + void setViewSize (const CRect& rect, bool invalid = true) override; + void parentSizeChanged () override; + void onScaleFactorChanged (CFrame* frame, double newScaleFactor) override; + void setMouseEnabled (bool enable = true) override; + + ExternalView::IView* getExternalView () const override; + + CLASS_METHODS_NOCOPY (CExternalControl, CControl) +private: + void draw (CDrawContext* pContext) override {} + bool getFocusPath (CGraphicsPath& outPath) override; + + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/cfont.h b/vstgui/lib/cfont.h index 5588e6626..b4688ed41 100644 --- a/vstgui/lib/cfont.h +++ b/vstgui/lib/cfont.h @@ -11,18 +11,6 @@ namespace VSTGUI { -//----------- -// @brief Text Face -//----------- -enum CTxtFace -{ - kNormalFace = 0, - kBoldFace = 1 << 1, - kItalicFace = 1 << 2, - kUnderlineFace = 1 << 3, - kStrikethroughFace = 1 << 4 -}; - //----------------------------------------------------------------------------- // CFontDesc Declaration //! @brief font class diff --git a/vstgui/lib/cframe.cpp b/vstgui/lib/cframe.cpp index 5e5e7f4b1..e809e95ee 100644 --- a/vstgui/lib/cframe.cpp +++ b/vstgui/lib/cframe.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace VSTGUI { @@ -1660,10 +1661,12 @@ IPlatformFrame* CFrame::getPlatformFrame () const } //----------------------------------------------------------------------------- -bool CFrame::platformDrawRect (CDrawContext* context, const CRect& rect) +void CFrame::platformDrawRects (const PlatformGraphicsDeviceContextPtr& context, double scaleFactor, + const std::vector& rects) { - drawRect (context, rect); - return true; + CDrawContext drawContext (context, getViewSize (), scaleFactor); + for (auto rect : rects) + drawRect (&drawContext, rect); } //----------------------------------------------------------------------------- diff --git a/vstgui/lib/cframe.h b/vstgui/lib/cframe.h index f5804721a..b946cb301 100644 --- a/vstgui/lib/cframe.h +++ b/vstgui/lib/cframe.h @@ -256,7 +256,8 @@ class CFrame final : public CViewContainer, public IPlatformFrameCallback void dispatchNewScaleFactor (double newScaleFactor); // platform frame - bool platformDrawRect (CDrawContext* context, const CRect& rect) override; + void platformDrawRects (const PlatformGraphicsDeviceContextPtr& context, double scaleFactor, + const std::vector& rects) override; void platformOnEvent (Event& event) override; DragOperation platformOnDragEnter (DragEventData data) override; DragOperation platformOnDragMove (DragEventData data) override; diff --git a/vstgui/lib/cgraphicspath.cpp b/vstgui/lib/cgraphicspath.cpp index 9ca5c162b..f49d308da 100644 --- a/vstgui/lib/cgraphicspath.cpp +++ b/vstgui/lib/cgraphicspath.cpp @@ -178,6 +178,8 @@ CGradient* CGraphicsPath::createGradient (double color1Start, double color2Start //----------------------------------------------------------------------------- void CGraphicsPath::makePlatformGraphicsPath (PlatformGraphicsPathFillMode fillMode) { + if (!factory) + return; path = factory->createPath (fillMode); if (!path) return; diff --git a/vstgui/lib/clayeredviewcontainer.cpp b/vstgui/lib/clayeredviewcontainer.cpp index cf4f43267..e03512276 100644 --- a/vstgui/lib/clayeredviewcontainer.cpp +++ b/vstgui/lib/clayeredviewcontainer.cpp @@ -191,27 +191,35 @@ void CLayeredViewContainer::drawRect (CDrawContext* pContext, const CRect& updat auto drawsIntoBitmap = false; if (auto offscreenContext = dynamic_cast (pContext)) drawsIntoBitmap = offscreenContext->getBitmap () != nullptr; - if (layer && !drawsIntoBitmap) - layer->draw (pContext, updateRect); - else + if (!layer || drawsIntoBitmap) CViewContainer::drawRect (pContext, updateRect); } //----------------------------------------------------------------------------- -void CLayeredViewContainer::drawViewLayer (CDrawContext* context, const CRect& _dirtyRect) +void CLayeredViewContainer::drawViewLayerRects (const PlatformGraphicsDeviceContextPtr& context, + double scaleFactor, const std::vector& rects) { - CRect dirtyRect (_dirtyRect); - CGraphicsTransform drawTransform = getDrawTransform (); - drawTransform.inverse ().transform (dirtyRect); CRect visibleSize = getVisibleViewSize (); CRect viewSize = getViewSize (); CPoint p (viewSize.left < 0 ? viewSize.left - visibleSize.left : visibleSize.left, - viewSize.top < 0 ? viewSize.top - visibleSize.top : visibleSize.top); - dirtyRect.offset (p.x, p.y); - CDrawContext::Transform transform (*context, drawTransform * CGraphicsTransform ().translate (-p.x, -p.y)); - CViewContainer::drawRect (context, dirtyRect); + viewSize.top < 0 ? viewSize.top - visibleSize.top : visibleSize.top); + + auto surfaceSize = getViewSize (); + surfaceSize.originize (); + CDrawContext drawContext (context, surfaceSize, scaleFactor); + CDrawContext::Transform transform ( + drawContext, drawTransform * CGraphicsTransform ().translate (-p.x, -p.y)); + for (auto dirtyRect : rects) + { + drawTransform.inverse ().transform (dirtyRect); + dirtyRect.offset (p.x, p.y); + drawContext.saveGlobalState (); + drawContext.setClipRect (dirtyRect); + CViewContainer::drawRect (&drawContext, dirtyRect); + drawContext.restoreGlobalState (); + } } //----------------------------------------------------------------------------- diff --git a/vstgui/lib/clayeredviewcontainer.h b/vstgui/lib/clayeredviewcontainer.h index 73c958417..c69bd2ccb 100644 --- a/vstgui/lib/clayeredviewcontainer.h +++ b/vstgui/lib/clayeredviewcontainer.h @@ -43,7 +43,8 @@ class CLayeredViewContainer : public CViewContainer, //----------------------------------------------------------------------------- protected: void drawRect (CDrawContext* pContext, const CRect& updateRect) override; - void drawViewLayer (CDrawContext* context, const CRect& dirtyRect) override; + void drawViewLayerRects (const PlatformGraphicsDeviceContextPtr& context, double scaleFactor, + const std::vector& rects) override; void viewContainerTransformChanged (CViewContainer* container) override; void onScaleFactorChanged (CFrame* frame, double newScaleFactor) override; void updateLayerSize (); diff --git a/vstgui/lib/coffscreencontext.cpp b/vstgui/lib/coffscreencontext.cpp index a92120d2f..0939c0e87 100644 --- a/vstgui/lib/coffscreencontext.cpp +++ b/vstgui/lib/coffscreencontext.cpp @@ -6,6 +6,7 @@ #include "cframe.h" #include "cbitmap.h" #include "platform/platformfactory.h" +#include "platform/iplatformgraphicsdevice.h" namespace VSTGUI { @@ -22,6 +23,15 @@ COffscreenContext::COffscreenContext (const CRect& surfaceRect) { } +//----------------------------------------------------------------------------- +COffscreenContext::COffscreenContext (const PlatformGraphicsDeviceContextPtr device, + const CRect& surfaceRect, + const PlatformBitmapPtr& platformBitmap) +: CDrawContext (device, surfaceRect, platformBitmap->getScaleFactor ()) +, bitmap (makeOwned (platformBitmap)) +{ +} + //----------------------------------------------------------------------------- void COffscreenContext::copyFrom (CDrawContext *pContext, CRect destRect, CPoint srcOffset) { @@ -41,7 +51,22 @@ SharedPointer COffscreenContext::create (CFrame* frame, CCoor SharedPointer COffscreenContext::create (const CPoint& size, double scaleFactor) { if (size.x >= 1. && size.y >= 1.) - return getPlatformFactory ().createOffscreenContext (size, scaleFactor); + { + if (auto graphicsDevice = + getPlatformFactory ().getGraphicsDeviceFactory ().getDeviceForScreen ( + DefaultScreenIdentifier)) + { + if (auto bitmap = getPlatformFactory ().createBitmap (size * scaleFactor)) + { + bitmap->setScaleFactor (scaleFactor); + if (auto context = graphicsDevice->createBitmapContext (bitmap)) + { + CRect surfaceRect (CPoint (), size * scaleFactor); + return makeOwned (context, surfaceRect, bitmap); + } + } + } + } return nullptr; } diff --git a/vstgui/lib/coffscreencontext.h b/vstgui/lib/coffscreencontext.h index e4ef94f80..d140d2bd4 100644 --- a/vstgui/lib/coffscreencontext.h +++ b/vstgui/lib/coffscreencontext.h @@ -75,6 +75,9 @@ class COffscreenContext : public CDrawContext CBitmap* getBitmap () const { return bitmap; } + COffscreenContext (const PlatformGraphicsDeviceContextPtr device, const CRect& surfaceRect, + const PlatformBitmapPtr& platformBitmap); + protected: explicit COffscreenContext (CBitmap* bitmap); explicit COffscreenContext (const CRect& surfaceRect); diff --git a/vstgui/lib/controls/cfontchooser.cpp b/vstgui/lib/controls/cfontchooser.cpp index 988f7dba8..95785fcdd 100644 --- a/vstgui/lib/controls/cfontchooser.cpp +++ b/vstgui/lib/controls/cfontchooser.cpp @@ -4,6 +4,7 @@ #include "cfontchooser.h" #include "../cdatabrowser.h" +#include "../cdrawcontext.h" #include "cbuttons.h" #include "ctextedit.h" #include "cscrollbar.h" diff --git a/vstgui/lib/controls/cknob.cpp b/vstgui/lib/controls/cknob.cpp index 2a46ec27c..53f247a5f 100644 --- a/vstgui/lib/controls/cknob.cpp +++ b/vstgui/lib/controls/cknob.cpp @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -418,7 +418,7 @@ CKnob::CKnob (const CRect& size, IControlListener* listener, int32_t tag, CBitma { inset = 3; } - + colorShadowHandle = kGreyCColor; colorHandle = kWhiteCColor; coronaLineStyle = kLineOnOffDash; @@ -611,7 +611,7 @@ void CKnob::drawHandleAsLine (CDrawContext* pContext) const pContext->setLineStyle (CLineStyle (CLineStyle::kLineCapRound)); pContext->setDrawMode (kAntiAliasing | kNonIntegralMode); pContext->drawLine (where, origin); - + where.offset (1, -1); origin.offset (1, -1); pContext->setFrameColor (colorHandle); diff --git a/vstgui/lib/controls/cmoviebitmap.cpp b/vstgui/lib/controls/cmoviebitmap.cpp index f7cfa2fe1..d55fe9799 100644 --- a/vstgui/lib/controls/cmoviebitmap.cpp +++ b/vstgui/lib/controls/cmoviebitmap.cpp @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -79,7 +79,6 @@ void CMovieBitmap::draw (CDrawContext *pContext) else { #if VSTGUI_ENABLE_DEPRECATED_METHODS - #include "../private/disabledeprecatedmessage.h" CPoint where (offset.x, offset.y); if (useLegacyFrameCalculation) diff --git a/vstgui/lib/controls/csearchtextedit.cpp b/vstgui/lib/controls/csearchtextedit.cpp index 48a36353e..329da775d 100644 --- a/vstgui/lib/controls/csearchtextedit.cpp +++ b/vstgui/lib/controls/csearchtextedit.cpp @@ -6,6 +6,7 @@ #include "../cframe.h" #include "../cgraphicspath.h" +#include "../cdrawcontext.h" namespace VSTGUI { diff --git a/vstgui/lib/controls/ctextedit.cpp b/vstgui/lib/controls/ctextedit.cpp index 082dae08e..bf62350c9 100644 --- a/vstgui/lib/controls/ctextedit.cpp +++ b/vstgui/lib/controls/ctextedit.cpp @@ -5,6 +5,7 @@ #include "ctextedit.h" #include "itexteditlistener.h" #include "../cframe.h" +#include "../cdrawcontext.h" #include "../events.h" #include "../platform/iplatformframe.h" #include diff --git a/vstgui/lib/controls/ctextlabel.cpp b/vstgui/lib/controls/ctextlabel.cpp index 2a3659e3b..3e3e29fe6 100644 --- a/vstgui/lib/controls/ctextlabel.cpp +++ b/vstgui/lib/controls/ctextlabel.cpp @@ -382,7 +382,8 @@ void CMultiLineTextLabel::calculateWrapLine (CDrawContext* context, break; auto tmpEnd = pos; UTF8String tmp ({start.base (), (++tmpEnd).base ()}); - auto width = fontPainter->getStringWidth (context, tmp.getPlatformString ()); + auto width = fontPainter->getStringWidth ( + context ? context->getPlatformDeviceContext () : nullptr, tmp.getPlatformString ()); if (width > maxWidth) { if (lastSeparator == element.first.end ()) @@ -428,7 +429,8 @@ void CMultiLineTextLabel::recalculateLines (CDrawContext* context) while (std::getline (stream, line, '\n')) { UTF8String str (std::move (line)); - auto width = fontPainter->getStringWidth (context, str.getPlatformString ()); + auto width = fontPainter->getStringWidth ( + context ? context->getPlatformDeviceContext () : nullptr, str.getPlatformString ()); elements.emplace_back (std::move (str), width); } diff --git a/vstgui/lib/iexternalview.h b/vstgui/lib/iexternalview.h new file mode 100644 index 000000000..60ded0164 --- /dev/null +++ b/vstgui/lib/iexternalview.h @@ -0,0 +1,146 @@ +// 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 +#include + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace ExternalView { + +//------------------------------------------------------------------------ +enum class PlatformViewType : uint32_t +{ + HWND, + NSView, + + Unknown +}; + +//------------------------------------------------------------------------ +struct IntPoint +{ + int64_t x {0}; + int64_t y {0}; +}; + +//------------------------------------------------------------------------ +struct IntSize +{ + int64_t width {0}; + int64_t height {0}; +}; + +//------------------------------------------------------------------------ +struct IntRect +{ + IntPoint origin; + IntSize size; +}; + +//------------------------------------------------------------------------ +/** interface for embedding views from external view systems + * + * @ingroup new_in_4_13 + */ +struct IView +{ + virtual ~IView () noexcept = default; + + /** check if the view supports the platform view type */ + virtual bool platformViewTypeSupported (PlatformViewType type) = 0; + /** attach the view to the parent */ + virtual bool attach (void* parent, PlatformViewType parentViewType) = 0; + /** remove the view from its parent */ + virtual bool remove () = 0; + + /** set the size and position of the view + * + * the coordinate system for the parameters used are system native, + * this means on Windows the scale factor is already applied to the coordinates + * and on macOS they are not (as with all NSViews) + * + * the visible rectangle is the rectangle clipped to all its parent views + * while the frame rectangle is the full size of the view if it would not + * be clipped. this way it is possible to support views inside of scroll views + * and the like. See the examples how this is done. + */ + virtual void setViewSize (IntRect frame, IntRect visible) = 0; + /** set the scale factor in use */ + virtual void setContentScaleFactor (double scaleFactor) = 0; + + /** enable or disable mouse handling for the view */ + virtual void setMouseEnabled (bool state) = 0; + + /** the view should take focus */ + virtual void takeFocus () = 0; + /** the view should loose focus */ + virtual void looseFocus () = 0; + + using TookFocusCallback = std::function; + /** a callback the embedder sets on the view to get notified when the view took focus */ + virtual void setTookFocusCallback (const TookFocusCallback& callback) = 0; +}; + +//------------------------------------------------------------------------ +struct IControlViewExtension +{ + using ValueBeginEditCallback = std::function; + using ValueEndEditCallback = std::function; + using ValuePerformEditCallback = std::function; + struct EditCallbacks + { + ValueBeginEditCallback beginEdit; + ValuePerformEditCallback performEdit; + ValueEndEditCallback endEdit; + }; + + virtual bool setValue (double value) = 0; + virtual bool setEditCallbacks (const EditCallbacks& callbacks) = 0; +}; + +//------------------------------------------------------------------------ +/** interface for view embedder classes + * + * @ingroup new_in_4_13 + */ +struct IViewEmbedder +{ + virtual ~IViewEmbedder () noexcept = default; + + /** returns the embedded view or nullptr if it has none */ + virtual IView* getExternalView () const = 0; +}; + +//------------------------------------------------------------------------ +/** adapter for the IView interface + * + * @ingroup new_in_4_13 + */ +struct ViewAdapter : IView +{ + bool platformViewTypeSupported (PlatformViewType type) override { return false; } + bool attach (void* parent, PlatformViewType parentViewType) override { return false; } + bool remove () override { return false; } + 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 ControlViewAdapter : ViewAdapter, + IControlViewExtension +{ + bool setValue (double value) override { return false; } + bool setEditCallbacks (const EditCallbacks& callbacks) override { return false; } +}; + +//------------------------------------------------------------------------ +} // ExternalView +} // VSTGUI diff --git a/vstgui/lib/platform/common/genericoptionmenu.cpp b/vstgui/lib/platform/common/genericoptionmenu.cpp index 9188e6a20..261997c2b 100644 --- a/vstgui/lib/platform/common/genericoptionmenu.cpp +++ b/vstgui/lib/platform/common/genericoptionmenu.cpp @@ -60,6 +60,7 @@ class DataSource : public DataBrowserDelegateAdapter, if (maxWidth >= 0.) return maxWidth; auto context = COffscreenContext::create ({1., 1.}); + context->setFont (theme.font); maxWidth = 0.; maxTitleWidth = 0.; hasRightMargin = false; diff --git a/vstgui/lib/platform/common/generictextedit.cpp b/vstgui/lib/platform/common/generictextedit.cpp index e1803d5f2..f6725b971 100644 --- a/vstgui/lib/platform/common/generictextedit.cpp +++ b/vstgui/lib/platform/common/generictextedit.cpp @@ -10,6 +10,7 @@ #include "../../cvstguitimer.h" #include "../../cdropsource.h" #include "../../events.h" +#include "../../cdrawcontext.h" #include #include diff --git a/vstgui/lib/platform/iplatformfont.h b/vstgui/lib/platform/iplatformfont.h index 2bdf9be6b..69d1666ac 100644 --- a/vstgui/lib/platform/iplatformfont.h +++ b/vstgui/lib/platform/iplatformfont.h @@ -18,10 +18,11 @@ class IFontPainter public: virtual ~IFontPainter () noexcept = default; - virtual void drawString (CDrawContext* context, IPlatformString* string, const CPoint& p, + virtual void drawString (const PlatformGraphicsDeviceContextPtr& context, + IPlatformString* string, const CPoint& p, const CColor& color, bool antialias = true) const = 0; - virtual CCoord getStringWidth (CDrawContext* context, IPlatformString* string, - bool antialias = true) const = 0; + virtual CCoord getStringWidth (const PlatformGraphicsDeviceContextPtr& context, + IPlatformString* string, bool antialias = true) const = 0; }; //----------------------------------------------------------------------------- diff --git a/vstgui/lib/platform/iplatformframecallback.h b/vstgui/lib/platform/iplatformframecallback.h index 18b395933..8b6c0737d 100644 --- a/vstgui/lib/platform/iplatformframecallback.h +++ b/vstgui/lib/platform/iplatformframecallback.h @@ -30,7 +30,10 @@ enum class PlatformType : int32_t { class IPlatformFrameCallback { public: - virtual bool platformDrawRect (CDrawContext* context, const CRect& rect) = 0; + virtual ~IPlatformFrameCallback () = default; + + virtual void platformDrawRects (const PlatformGraphicsDeviceContextPtr& context, + double scaleFactor, const std::vector& rects) = 0; virtual void platformOnEvent (Event& event) = 0; diff --git a/vstgui/lib/platform/iplatformgraphicsdevice.h b/vstgui/lib/platform/iplatformgraphicsdevice.h new file mode 100644 index 000000000..c29baf2b0 --- /dev/null +++ b/vstgui/lib/platform/iplatformgraphicsdevice.h @@ -0,0 +1,124 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE + +#pragma once + +#include "../vstguifwd.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { + +//------------------------------------------------------------------------ +enum class PlatformGraphicsDrawStyle : uint32_t +{ + Stroked, + Filled, + FilledAndStroked +}; + +//------------------------------------------------------------------------ +enum class PlatformGraphicsPathDrawMode : uint32_t +{ + Filled, + FilledEvenOdd, + Stroked +}; + +//------------------------------------------------------------------------ +using TransformMatrix = CGraphicsTransform; + +//------------------------------------------------------------------------ +struct ScreenInfo +{ + using Identifier = uint32_t; + /* + Identifier identifier; + uint32_t width; + uint32_t height; + */ +}; +static constexpr const ScreenInfo::Identifier DefaultScreenIdentifier = 0u; + +//------------------------------------------------------------------------ +class IPlatformGraphicsDeviceFactory +{ +public: + virtual ~IPlatformGraphicsDeviceFactory () noexcept = default; + + virtual PlatformGraphicsDevicePtr getDeviceForScreen (ScreenInfo::Identifier screen) const = 0; +}; + +//------------------------------------------------------------------------ +class IPlatformGraphicsDevice +{ +public: + virtual ~IPlatformGraphicsDevice () noexcept = default; + + virtual PlatformGraphicsDeviceContextPtr + createBitmapContext (const PlatformBitmapPtr& bitmap) const = 0; +}; + +//------------------------------------------------------------------------ +class IPlatformGraphicsDeviceContext +{ +public: + virtual ~IPlatformGraphicsDeviceContext () noexcept = default; + + virtual const IPlatformGraphicsDevice& getDevice () const = 0; + virtual PlatformGraphicsPathFactoryPtr getGraphicsPathFactory () const = 0; + + virtual bool beginDraw () const = 0; + virtual bool endDraw () const = 0; + // draw commands + virtual bool drawLine (LinePair line) const = 0; + virtual bool drawLines (const LineList& lines) const = 0; + virtual bool drawPolygon (const PointList& polygonPointList, + PlatformGraphicsDrawStyle drawStyle) const = 0; + virtual bool drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const = 0; + virtual bool drawArc (CRect rect, double startAngle1, double endAngle2, + PlatformGraphicsDrawStyle drawStyle) const = 0; + virtual bool drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const = 0; + virtual bool drawPoint (CPoint point, CColor color) const = 0; + virtual bool drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, double alpha, + BitmapInterpolationQuality quality) const = 0; + virtual bool clearRect (CRect rect) const = 0; + virtual bool drawGraphicsPath (IPlatformGraphicsPath& path, PlatformGraphicsPathDrawMode mode, + TransformMatrix* transformation) const = 0; + virtual bool fillLinearGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint startPoint, CPoint endPoint, bool evenOdd, + TransformMatrix* transformation) const = 0; + virtual bool fillRadialGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint center, CCoord radius, CPoint originOffset, + bool evenOdd, TransformMatrix* transformation) const = 0; + // state + virtual void saveGlobalState () const = 0; + virtual void restoreGlobalState () const = 0; + virtual void setLineStyle (const CLineStyle& style) const = 0; + virtual void setLineWidth (CCoord width) const = 0; + virtual void setDrawMode (CDrawMode mode) const = 0; + virtual void setClipRect (CRect clip) const = 0; + virtual void setFillColor (CColor color) const = 0; + virtual void setFrameColor (CColor color) const = 0; + virtual void setGlobalAlpha (double newAlpha) const = 0; + virtual void setTransformMatrix (const TransformMatrix& tm) const = 0; + + // extension + virtual const IPlatformGraphicsDeviceContextBitmapExt* asBitmapExt () const = 0; +}; + +//------------------------------------------------------------------------ +class IPlatformGraphicsDeviceContextBitmapExt +{ +public: + virtual ~IPlatformGraphicsDeviceContextBitmapExt () noexcept = default; + + virtual bool drawBitmapNinePartTiled (IPlatformBitmap& bitmap, CRect dest, + const CNinePartTiledDescription& desc, double alpha, + BitmapInterpolationQuality quality) const = 0; + virtual bool fillRectWithBitmap (IPlatformBitmap& bitmap, CRect srcRect, CRect dstRect, + double alpha, BitmapInterpolationQuality quality) const = 0; +}; + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/iplatformtextedit.h b/vstgui/lib/platform/iplatformtextedit.h index 224ce2a1c..7771a0782 100644 --- a/vstgui/lib/platform/iplatformtextedit.h +++ b/vstgui/lib/platform/iplatformtextedit.h @@ -9,7 +9,7 @@ #include "../cfont.h" #include "../ccolor.h" #include "../crect.h" -#include "../cdrawcontext.h" +#include "../cdrawdefs.h" struct VstKeyCode; diff --git a/vstgui/lib/platform/iplatformviewlayer.h b/vstgui/lib/platform/iplatformviewlayer.h index e1bbfc44d..703a32cc4 100644 --- a/vstgui/lib/platform/iplatformviewlayer.h +++ b/vstgui/lib/platform/iplatformviewlayer.h @@ -14,8 +14,9 @@ class IPlatformViewLayerDelegate public: virtual ~IPlatformViewLayerDelegate () noexcept = default; - /** dirtyRect is in client coordinated (top-left is 0, 0) */ - virtual void drawViewLayer (CDrawContext* context, const CRect& dirtyRect) = 0; + /** rects are in client coordinates (top-left is 0, 0) */ + virtual void drawViewLayerRects (const PlatformGraphicsDeviceContextPtr& context, + double scaleFactor, const std::vector& rects) = 0; }; //----------------------------------------------------------------------------- @@ -28,7 +29,6 @@ class IPlatformViewLayer : public AtomicReferenceCounted virtual void setSize (const CRect& size) = 0; virtual void setZIndex (uint32_t zIndex) = 0; virtual void setAlpha (float alpha) = 0; - virtual void draw (CDrawContext* context, const CRect& updateRect) = 0; virtual void onScaleFactorChanged (double newScaleFactor) = 0; }; diff --git a/vstgui/lib/platform/linux/cairocontext.cpp b/vstgui/lib/platform/linux/cairocontext.cpp deleted file mode 100644 index 578ded239..000000000 --- a/vstgui/lib/platform/linux/cairocontext.cpp +++ /dev/null @@ -1,562 +0,0 @@ -// 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 "cairocontext.h" -#include "../../cbitmap.h" -#include "../../cgradient.h" -#include "cairobitmap.h" -#include "cairogradient.h" -#include "cairopath.h" - -//------------------------------------------------------------------------ -namespace VSTGUI { -namespace Cairo { - -//------------------------------------------------------------------------ -namespace { - -struct SaveCairoState -{ - SaveCairoState (ContextHandle& h) : h (h) { cairo_save (h); } - ~SaveCairoState () { cairo_restore (h); } - -private: - ContextHandle& h; -}; - -//------------------------------------------------------------------------ -void checkCairoStatus (const ContextHandle& handle) -{ -#if DEBUG - auto status = cairo_status (handle); - if (status != CAIRO_STATUS_SUCCESS) - { - auto msg = cairo_status_to_string (status); - DebugPrint ("%s\n", msg); - } -#endif -} - -//------------------------------------------------------------------------ -cairo_matrix_t convert (CGraphicsTransform& ct) -{ - return {ct.m11, ct.m21, ct.m12, ct.m22, ct.dx, ct.dy}; -} - -//----------------------------------------------------------------------------- -inline bool needPixelAlignment (CDrawMode mode) -{ - return (mode.integralMode () && mode.modeIgnoringIntegralMode () == kAntiAliasing); -} - -//------------------------------------------------------------------------ -} // anonymous - -//------------------------------------------------------------------------ -DrawBlock::DrawBlock (Context& context) : context (context) -{ - auto ct = context.getCurrentTransform (); - CRect clip = context.getCurrentStateClipRect (); - if (clip.isEmpty ()) - { - clipIsEmpty = true; - } - else - { - cairo_save (context.getCairo ()); - cairo_rectangle (context.getCairo (), clip.left, clip.top, clip.getWidth (), - clip.getHeight ()); - cairo_clip (context.getCairo ()); - auto matrix = convert (ct); - cairo_set_matrix (context.getCairo (), &matrix); - auto antialiasMode = context.getDrawMode ().modeIgnoringIntegralMode () == kAntiAliasing - ? CAIRO_ANTIALIAS_BEST - : CAIRO_ANTIALIAS_NONE; - cairo_set_antialias (context.getCairo (), antialiasMode); - } -} - -//------------------------------------------------------------------------ -DrawBlock::~DrawBlock () -{ - if (!clipIsEmpty) - { - cairo_restore (context.getCairo ()); - } -} - -//------------------------------------------------------------------------ -DrawBlock DrawBlock::begin (Context& context) -{ - return DrawBlock (context); -} - -//----------------------------------------------------------------------------- -Context::Context (const CRect& rect, const SurfaceHandle& surface) : super (rect), surface (surface) -{ - init (); -} - -//----------------------------------------------------------------------------- -Context::Context (Bitmap* bitmap) : super (new CBitmap (bitmap)), surface (bitmap->getSurface ()) -{ - init (); -} - -//----------------------------------------------------------------------------- -Context::Context (CRect r, cairo_t* context) : super (r) -{ - cr = ContextHandle {cairo_reference (context)}; - init (); -} - -//----------------------------------------------------------------------------- -Context::~Context () {} - -//----------------------------------------------------------------------------- -void Context::init () -{ - if (surface) - cr.assign (cairo_create (surface)); - super::init (); -} - -//----------------------------------------------------------------------------- -CRect Context::getCurrentStateClipRect () const -{ - return getCurrentState ().clipRect; -} - -//----------------------------------------------------------------------------- -void Context::beginDraw () -{ - super::beginDraw (); - cairo_save (cr); - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -void Context::endDraw () -{ - cairo_restore (cr); - if (surface) - cairo_surface_flush (surface); - checkCairoStatus (cr); - super::endDraw (); -} - -//----------------------------------------------------------------------------- -void Context::saveGlobalState () -{ - super::saveGlobalState (); -} - -//----------------------------------------------------------------------------- -void Context::restoreGlobalState () -{ - super::restoreGlobalState (); -} - -//----------------------------------------------------------------------------- -void Context::setSourceColor (CColor color) -{ - auto alpha = color.normAlpha (); - alpha *= getGlobalAlpha (); - cairo_set_source_rgba (cr, color.normRed (), color.normGreen (), - color.normBlue (), alpha); - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -void Context::setupCurrentStroke () -{ - auto lineWidth = getLineWidth (); - cairo_set_line_width (cr, lineWidth); - const auto& style = getLineStyle (); - if (!style.getDashLengths ().empty ()) - { - auto lengths = style.getDashLengths (); - for (auto& l : lengths) - l *= lineWidth; - cairo_set_dash (cr, lengths.data (), lengths.size (), style.getDashPhase ()); - } - cairo_line_cap_t lineCap; - switch (style.getLineCap ()) - { - case CLineStyle::kLineCapButt: - { - lineCap = CAIRO_LINE_CAP_BUTT; - break; - } - case CLineStyle::kLineCapRound: - { - lineCap = CAIRO_LINE_CAP_ROUND; - break; - } - case CLineStyle::kLineCapSquare: - { - lineCap = CAIRO_LINE_CAP_SQUARE; - break; - } - } - cairo_set_line_cap (cr, lineCap); - cairo_line_join_t lineJoin; - switch (style.getLineJoin ()) - { - case CLineStyle::kLineJoinBevel: - { - lineJoin = CAIRO_LINE_JOIN_BEVEL; - break; - } - case CLineStyle::kLineJoinMiter: - { - lineJoin = CAIRO_LINE_JOIN_MITER; - break; - } - case CLineStyle::kLineJoinRound: - { - lineJoin = CAIRO_LINE_JOIN_ROUND; - break; - } - } - - cairo_set_line_join (cr, lineJoin); -} - -//----------------------------------------------------------------------------- -void Context::draw (CDrawStyle drawStyle) -{ - switch (drawStyle) - { - case kDrawStroked: - { - setupCurrentStroke (); - setSourceColor (getFrameColor ()); - cairo_stroke (cr); - break; - } - case kDrawFilled: - { - setSourceColor (getFillColor ()); - cairo_fill (cr); - break; - } - case kDrawFilledAndStroked: - { - setSourceColor (getFillColor ()); - cairo_fill_preserve (cr); - setupCurrentStroke (); - setSourceColor (getFrameColor ()); - cairo_stroke (cr); - break; - } - } - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -void Context::drawLine (const CDrawContext::LinePair& line) -{ - if (auto cd = DrawBlock::begin (*this)) - { - setupCurrentStroke (); - setSourceColor (getFrameColor ()); - if (getDrawMode ().integralMode ()) - { - CPoint start = pixelAlign (getCurrentTransform (), line.first); - CPoint end = pixelAlign (getCurrentTransform (), line.second); - cairo_move_to (cr, start.x, start.y); - cairo_line_to (cr, end.x, end.y); - } - else - { - cairo_move_to (cr, line.first.x, line.first.y); - cairo_line_to (cr, line.second.x, line.second.y); - } - cairo_stroke (cr); - } - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -void Context::drawLines (const CDrawContext::LineList& lines) -{ - if (auto cd = DrawBlock::begin (*this)) - { - setupCurrentStroke (); - setSourceColor (getFrameColor ()); - if (getDrawMode ().integralMode ()) - { - for (auto& line : lines) - { - CPoint start = pixelAlign (getCurrentTransform (), line.first); - CPoint end = pixelAlign (getCurrentTransform (), line.second); - cairo_move_to (cr, start.x, start.y); - cairo_line_to (cr, end.x, end.y); - cairo_stroke (cr); - } - } - else - { - for (auto& line : lines) - { - cairo_move_to (cr, line.first.x, line.first.y); - cairo_line_to (cr, line.second.x, line.second.y); - cairo_stroke (cr); - } - } - } -} - -//----------------------------------------------------------------------------- -void Context::drawPolygon (const CDrawContext::PointList& polygonPointList, - const CDrawStyle drawStyle) -{ - if (polygonPointList.size () < 2) - return; - - if (auto cd = DrawBlock::begin (*this)) - { - auto& last = polygonPointList.back (); - cairo_move_to (cr, last.x, last.y); - for (auto& it : polygonPointList) - cairo_line_to (cr, it.x, it.y); - - draw (drawStyle); - } -} - -//----------------------------------------------------------------------------- -void Context::drawRect (const CRect& rect, const CDrawStyle drawStyle) -{ - if (auto cd = DrawBlock::begin (*this)) - { - CRect r (rect); - if (needPixelAlignment (getDrawMode ())) - { - r = pixelAlign (getCurrentTransform (), r); - cairo_rectangle (cr, r.left, r.top, r.getWidth (), r.getHeight ()); - } - else - cairo_rectangle (cr, r.left + 0.5, r.top + 0.5, r.getWidth () - 0.5, - r.getHeight () - 0.5); - draw (drawStyle); - } -} - -//----------------------------------------------------------------------------- -void Context::drawArc (const CRect& rect, const float startAngle1, const float endAngle2, - const CDrawStyle drawStyle) -{ - if (auto cd = DrawBlock::begin (*this)) - { - CPoint center = rect.getCenter (); - cairo_translate (cr, center.x, center.y); - cairo_scale (cr, 2.0 / rect.getWidth (), 2.0 / rect.getHeight ()); - cairo_arc (cr, 0, 0, 1, startAngle1, endAngle2); - draw (drawStyle); - } -} - -//----------------------------------------------------------------------------- -void Context::drawEllipse (const CRect& rect, const CDrawStyle drawStyle) -{ - if (auto cd = DrawBlock::begin (*this)) - { - CPoint center = rect.getCenter (); - cairo_translate (cr, center.x, center.y); - cairo_scale (cr, 2.0 / rect.getWidth (), 2.0 / rect.getHeight ()); - cairo_arc (cr, 0, 0, 1, 0, 2 * M_PI); - draw (drawStyle); - } -} - -//----------------------------------------------------------------------------- -void Context::drawPoint (const CPoint& point, const CColor& color) -{ - if (auto cd = DrawBlock::begin (*this)) - { - setSourceColor (color); - cairo_rectangle (cr, point.x + 0.5, point.y + 0.5, 1, 1); - cairo_fill (cr); - } - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -void Context::drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset, float alpha) -{ - if (auto cd = DrawBlock::begin (*this)) - { - double transformedScaleFactor = getScaleFactor (); - CGraphicsTransform t = getCurrentTransform (); - if (t.m11 == t.m22 && t.m12 == 0 && t.m21 == 0) - transformedScaleFactor *= t.m11; - auto cairoBitmap = - bitmap->getBestPlatformBitmapForScaleFactor (transformedScaleFactor).cast (); - if (cairoBitmap) - { - cairo_translate (cr, dest.left, dest.top); - cairo_rectangle (cr, 0, 0, dest.getWidth (), dest.getHeight ()); - cairo_clip (cr); - - // Setup a pattern for scaling bitmaps and take it as source afterwards. - auto pattern = cairo_pattern_create_for_surface (cairoBitmap->getSurface ()); - cairo_matrix_t matrix; - cairo_pattern_get_matrix (pattern, &matrix); - cairo_matrix_init_scale (&matrix, cairoBitmap->getScaleFactor (), - cairoBitmap->getScaleFactor ()); - cairo_matrix_translate (&matrix, offset.x, offset.y); - cairo_pattern_set_matrix (pattern, &matrix); - cairo_set_source (cr, pattern); - - cairo_rectangle (cr, -offset.x, -offset.y, dest.getWidth () + offset.x, - dest.getHeight () + offset.y); - alpha *= getGlobalAlpha (); - if (alpha != 1.f) - { - cairo_paint_with_alpha (cr, alpha); - } - else - { - cairo_fill (cr); - } - - cairo_pattern_destroy (pattern); - } - } - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -void Context::clearRect (const CRect& rect) -{ - if (auto cd = DrawBlock::begin (*this)) - { - cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); - cairo_rectangle (cr, rect.left, rect.top, rect.getWidth (), rect.getHeight ()); - cairo_fill (cr); - } - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -CGraphicsPath* Context::createGraphicsPath () -{ - if (!graphicsPathFactory) - graphicsPathFactory = std::make_shared (cr); - return new CGraphicsPath (graphicsPathFactory); -} - -//----------------------------------------------------------------------------- -CGraphicsPath* Context::createTextPath (const CFontRef font, UTF8StringPtr text) -{ -#warning TODO: Implementation - return nullptr; -} - -//----------------------------------------------------------------------------- -void Context::drawGraphicsPath (CGraphicsPath* path, CDrawContext::PathDrawMode mode, - CGraphicsTransform* transformation) -{ - if (path) - { - auto graphicsPath = dynamic_cast ( - path->getPlatformPath (PlatformGraphicsPathFillMode::Ignored).get ()); - if (!graphicsPath) - return; - if (auto cd = DrawBlock::begin (*this)) - { - std::unique_ptr alignedPath; - if (needPixelAlignment (getDrawMode ())) - alignedPath = graphicsPath->copyPixelAlign (getCurrentTransform ()); - auto p = alignedPath ? alignedPath->getCairoPath () : graphicsPath->getCairoPath (); - if (transformation) - { - cairo_matrix_t currentMatrix; - cairo_matrix_t resultMatrix; - auto matrix = convert (*transformation); - cairo_get_matrix (cr, ¤tMatrix); - cairo_matrix_multiply (&resultMatrix, &matrix, ¤tMatrix); - cairo_set_matrix (cr, &resultMatrix); - } - cairo_append_path (cr, p); - switch (mode) - { - case PathDrawMode::kPathFilled: - { - setSourceColor (getFillColor ()); - cairo_fill (cr); - break; - } - case PathDrawMode::kPathFilledEvenOdd: - { - setSourceColor (getFillColor ()); - cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); - cairo_fill (cr); - break; - } - case PathDrawMode::kPathStroked: - { - setupCurrentStroke (); - setSourceColor (getFrameColor ()); - cairo_stroke (cr); - break; - } - } - } - } - checkCairoStatus (cr); -} - -//----------------------------------------------------------------------------- -void Context::fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, - const CPoint& startPoint, const CPoint& endPoint, bool evenOdd, - CGraphicsTransform* transformation) -{ - if (path) - { - auto graphicsPath = dynamic_cast ( - path->getPlatformPath (PlatformGraphicsPathFillMode::Ignored).get ()); - if (!graphicsPath) - return; - std::unique_ptr alignedPath; - if (needPixelAlignment (getDrawMode ())) - alignedPath = graphicsPath->copyPixelAlign (getCurrentTransform ()); - if (auto cairoGradient = dynamic_cast (gradient.getPlatformGradient ().get ())) - { - if (auto cd = DrawBlock::begin (*this)) - { - auto p = alignedPath ? alignedPath->getCairoPath () : graphicsPath->getCairoPath (); - cairo_append_path (cr, p); - cairo_set_source (cr, cairoGradient->getLinearGradient (startPoint, endPoint)); - if (evenOdd) - { - cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); - cairo_fill (cr); - } - else - { - cairo_fill (cr); - } - } - } - } -} - -//----------------------------------------------------------------------------- -void Context::fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, - const CPoint& center, CCoord radius, const CPoint& originOffset, - bool evenOdd, CGraphicsTransform* transformation) -{ -#warning TODO: Implementation - auto cd = DrawBlock::begin (*this); - if (cd) - { - } -} - -//----------------------------------------------------------------------------- -} // Cairo -} // VSTGUI diff --git a/vstgui/lib/platform/linux/cairocontext.h b/vstgui/lib/platform/linux/cairocontext.h deleted file mode 100644 index 5cc75f881..000000000 --- a/vstgui/lib/platform/linux/cairocontext.h +++ /dev/null @@ -1,115 +0,0 @@ -// 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 "cairoutils.h" -#include "cairopath.h" - -#include "../../coffscreencontext.h" - -//------------------------------------------------------------------------ -namespace VSTGUI { -namespace Cairo { - -class Bitmap; - -//------------------------------------------------------------------------ -class Context : public COffscreenContext -{ -public: - using super = COffscreenContext; - - Context (const CRect& rect, const SurfaceHandle& surface); - Context (CRect r, cairo_t* context); - Context (Bitmap* bitmap); - - ~Context (); - - bool valid () const { return cr != nullptr; } - const SurfaceHandle& getSurface () const { return surface; } - const ContextHandle& getCairo () const { return cr; } - - void drawLine (const LinePair& line) override; - void drawLines (const LineList& lines) override; - void drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle) override; - void drawRect (const CRect& rect, const CDrawStyle drawStyle) override; - void drawArc (const CRect& rect, const float startAngle1, const float endAngle2, - const CDrawStyle drawStyle) override; - void drawEllipse (const CRect& rect, const CDrawStyle drawStyle) override; - void drawPoint (const CPoint& point, const CColor& color) override; - void drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset, - float alpha) override; - void clearRect (const CRect& rect) override; - CGraphicsPath* createGraphicsPath () override; - CGraphicsPath* createTextPath (const CFontRef font, UTF8StringPtr text) override; - void drawGraphicsPath (CGraphicsPath* path, PathDrawMode mode, - CGraphicsTransform* transformation) override; - void fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, - const CPoint& startPoint, const CPoint& endPoint, bool evenOdd, - CGraphicsTransform* transformation) override; - void fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& center, - CCoord radius, const CPoint& originOffset, bool evenOdd, - CGraphicsTransform* transformation) override; - - void saveGlobalState () override; - void restoreGlobalState () override; - - void beginDraw () override; - void endDraw () override; - - CRect getCurrentStateClipRect () const; -private: - void init () override; - void setSourceColor (CColor color); - void setupCurrentStroke (); - void draw (CDrawStyle drawstyle); - - SurfaceHandle surface; - ContextHandle cr; - - PlatformGraphicsPathFactoryPtr graphicsPathFactory; -}; - -//------------------------------------------------------------------------ -struct DrawBlock -{ - static DrawBlock begin (Context& context); - - ~DrawBlock (); - operator bool () { return !clipIsEmpty; } - -private: - explicit DrawBlock (Context& context); - Context& context; - bool clipIsEmpty {false}; -}; - -//----------------------------------------------------------------------------- -inline CPoint pixelAlign (const CGraphicsTransform& tm, const CPoint& p) -{ - auto obj = p; - tm.transform (obj); - obj.x = std::round (obj.x) - 0.5; - obj.y = std::round (obj.y) - 0.5; - tm.inverse ().transform (obj); - return obj; -} - -//----------------------------------------------------------------------------- -inline CRect pixelAlign (const CGraphicsTransform& tm, const CRect& r) -{ - auto obj = r; - tm.transform (obj); - obj.left = std::round (obj.left) - 0.5; - obj.right = std::round (obj.right) - 0.5; - obj.top = std::round (obj.top) - 0.5; - obj.bottom = std::round (obj.bottom) - 0.5; - tm.inverse ().transform (obj); - return obj; -} - -//------------------------------------------------------------------------ -} // Cairo -} // VSTGUI diff --git a/vstgui/lib/platform/linux/cairofont.cpp b/vstgui/lib/platform/linux/cairofont.cpp index f906e0e22..d1043bd54 100644 --- a/vstgui/lib/platform/linux/cairofont.cpp +++ b/vstgui/lib/platform/linux/cairofont.cpp @@ -3,8 +3,11 @@ // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE #include "cairofont.h" -#include "../../../lib/cstring.h" -#include "cairocontext.h" +#include "../../cstring.h" +#include "../../cfont.h" +#include "../../cpoint.h" +#include "../../ccolor.h" +#include "cairographicscontext.h" #include "linuxstring.h" #include "linuxfactory.h" #include @@ -17,9 +20,8 @@ namespace VSTGUI { namespace Cairo { namespace { -using PangoFontHandle = - Handle; +using PangoFontHandle = Handle; //------------------------------------------------------------------------ class FontList @@ -31,20 +33,11 @@ class FontList return gInstance; } - FcConfig* getFontConfig () - { - return fcConfig; - } + FcConfig* getFontConfig () { return fcConfig; } - PangoFontMap* getFontMap () - { - return fontMap; - } + PangoFontMap* getFontMap () { return fontMap; } - PangoContext* getFontContext () - { - return fontContext; - } + PangoContext* getFontContext () { return fontContext; } bool queryFont (UTF8StringPtr name, CCoord size, int32_t style, PangoFontHandle& fontHandle) { @@ -62,7 +55,7 @@ class FontList return font != nullptr; } - bool getAllFontFamilies(const FontFamilyCallback& callback) + bool getAllFontFamilies (const FontFamilyCallback& callback) { if (!fontContext) return false; @@ -85,7 +78,8 @@ class FontList fontMap = pango_cairo_font_map_new (); fontContext = pango_font_map_create_context (fontMap); - PangoFcFontMap *fcMap = G_TYPE_CHECK_INSTANCE_CAST (fontMap, PANGO_TYPE_FC_FONT_MAP, PangoFcFontMap); + PangoFcFontMap* fcMap = + G_TYPE_CHECK_INSTANCE_CAST (fontMap, PANGO_TYPE_FC_FONT_MAP, PangoFcFontMap); if (fcMap && FcInit () && (fcConfig = FcInitLoadConfigAndFonts ())) { if (auto linuxFactory = getPlatformFactory ().asLinuxFactory ()) @@ -94,7 +88,8 @@ class FontList if (!resourcePath.empty ()) { auto fontDir = resourcePath + "Fonts/"; - FcConfigAppFontAddDir (fcConfig, reinterpret_cast (fontDir.data ())); + FcConfigAppFontAddDir (fcConfig, + reinterpret_cast (fontDir.data ())); } pango_fc_font_map_set_config (fcMap, fcConfig); FcConfigDestroy (fcConfig); @@ -165,7 +160,7 @@ Font::Font (UTF8StringPtr name, const CCoord& size, const int32_t& style) pango_font_metrics_unref (metrics); } - PangoContext* context = fontList.getFontContext(); + PangoContext* context = fontList.getFontContext (); if (context) { PangoLayout* layout = pango_layout_new (context); @@ -196,114 +191,87 @@ Font::Font (UTF8StringPtr name, const CCoord& size, const int32_t& style) Font::~Font () {} //------------------------------------------------------------------------ -bool Font::valid () const -{ - return impl->font; -} +bool Font::valid () const { return impl->font; } //------------------------------------------------------------------------ -double Font::getAscent () const -{ - return impl->ascent; -} +double Font::getAscent () const { return impl->ascent; } //------------------------------------------------------------------------ -double Font::getDescent () const -{ - return impl->descent; -} +double Font::getDescent () const { return impl->descent; } //------------------------------------------------------------------------ -double Font::getLeading () const -{ - return impl->leading; -} +double Font::getLeading () const { return impl->leading; } //------------------------------------------------------------------------ -double Font::getCapHeight () const -{ - return impl->capHeight; -} +double Font::getCapHeight () const { return impl->capHeight; } //------------------------------------------------------------------------ -const IFontPainter* Font::getPainter () const -{ - return this; -} +const IFontPainter* Font::getPainter () const { return this; } //------------------------------------------------------------------------ -void Font::drawString (CDrawContext* context, IPlatformString* string, const CPoint& p, - bool antialias) const +void Font::drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, + const CPoint& p, const CColor& color, bool antialias) const { - if (auto cairoContext = dynamic_cast (context)) + auto cairoContext = std::dynamic_pointer_cast (context); + if (!cairoContext) + return; + auto linuxString = dynamic_cast (string); + if (!linuxString) + return; + PangoContext* pangoContext = FontList::instance ().getFontContext (); + if (!pangoContext) + return; + PangoLayout* layout = pango_layout_new (pangoContext); + if (!layout) + return; + + if (impl->font) { - if (auto cd = DrawBlock::begin (*cairoContext)) + PangoFontDescription* desc = pango_font_describe (impl->font); + if (desc) { - if (auto linuxString = dynamic_cast (string)) - { - auto color = cairoContext->getFontColor (); - const auto& cr = cairoContext->getCairo (); - auto alpha = color.normAlpha () * cairoContext->getGlobalAlpha (); - cairo_set_source_rgba (cr, color.normRed (), color.normGreen (), - color.normBlue (), alpha); - - PangoContext* context = FontList::instance ().getFontContext(); - if (context) - { - PangoLayout* layout = pango_layout_new (context); - if (layout) - { - if (impl->font) - { - PangoFontDescription* desc = pango_font_describe (impl->font); - if (desc) - { - pango_layout_set_font_description (layout, desc); - pango_font_description_free (desc); - } - } - - PangoAttrList* attrs = pango_attr_list_new (); - if (attrs) - { - if (impl->style & kUnderlineFace) - pango_attr_list_insert (attrs, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE)); - if (impl->style & kStrikethroughFace) - pango_attr_list_insert (attrs, pango_attr_strikethrough_new (true)); - pango_layout_set_attributes(layout, attrs); - pango_attr_list_unref (attrs); - } - - pango_layout_set_text (layout, linuxString->get ().c_str (), -1); - - PangoRectangle extents {}; - pango_layout_get_pixel_extents (layout, nullptr, &extents); - - PangoLayoutIter* iter = pango_layout_get_iter (layout); - CCoord baseline = 0.0; - if (iter) - { - baseline = pango_units_to_double (pango_layout_iter_get_baseline (iter)); - pango_layout_iter_free (iter); - } - - cairo_move_to (cr, p.x + extents.x, p.y + extents.y - baseline); - pango_cairo_show_layout (cr, layout); - g_object_unref (layout); - } - } - } + pango_layout_set_font_description (layout, desc); + pango_font_description_free (desc); } } + + PangoAttrList* attrs = pango_attr_list_new (); + if (attrs) + { + if (impl->style & kUnderlineFace) + pango_attr_list_insert (attrs, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE)); + if (impl->style & kStrikethroughFace) + pango_attr_list_insert (attrs, pango_attr_strikethrough_new (true)); + pango_layout_set_attributes (layout, attrs); + pango_attr_list_unref (attrs); + } + + pango_layout_set_text (layout, linuxString->get ().c_str (), -1); + + PangoRectangle extents {}; + pango_layout_get_pixel_extents (layout, nullptr, &extents); + + PangoLayoutIter* iter = pango_layout_get_iter (layout); + CCoord baseline = 0.0; + if (iter) + { + baseline = pango_units_to_double (pango_layout_iter_get_baseline (iter)); + pango_layout_iter_free (iter); + } + + cairoContext->drawPangoLayout (layout, {p.x + extents.x, p.y + extents.y - baseline}, color); + + g_object_unref (layout); } //------------------------------------------------------------------------ -CCoord Font::getStringWidth (CDrawContext* context, IPlatformString* string, bool antialias) const +CCoord Font::getStringWidth (const PlatformGraphicsDeviceContextPtr&, IPlatformString* string, + bool antialias) const { if (auto linuxString = dynamic_cast (string)) { int pangoWidth = 0; - PangoContext* context = FontList::instance ().getFontContext(); + PangoContext* context = FontList::instance ().getFontContext (); if (context) { PangoLayout* layout = pango_layout_new (context); diff --git a/vstgui/lib/platform/linux/cairofont.h b/vstgui/lib/platform/linux/cairofont.h index 2895eb736..5e6759cd5 100644 --- a/vstgui/lib/platform/linux/cairofont.h +++ b/vstgui/lib/platform/linux/cairofont.h @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -29,9 +29,9 @@ class Font double getCapHeight () const override; const IFontPainter* getPainter () const override; - void drawString (CDrawContext* context, IPlatformString* string, const CPoint& p, - bool antialias = true) const override; - CCoord getStringWidth (CDrawContext* context, IPlatformString* string, + void drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, + const CPoint& p, const CColor& color, bool antialias = true) const override; + CCoord getStringWidth (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, bool antialias = true) const override; static bool getAllFamilies (const FontFamilyCallback& callback); diff --git a/vstgui/lib/platform/linux/cairogradient.cpp b/vstgui/lib/platform/linux/cairogradient.cpp index f2abb35ad..9488d9916 100644 --- a/vstgui/lib/platform/linux/cairogradient.cpp +++ b/vstgui/lib/platform/linux/cairogradient.cpp @@ -22,11 +22,12 @@ void Gradient::changed () } //------------------------------------------------------------------------ -const PatternHandle& Gradient::getLinearGradient (CPoint start, CPoint end) +const PatternHandle& Gradient::getLinearGradient (CPoint start, CPoint end) const { if (!linearGradient || start != linearGradientStart || end != linearGradientEnd) { - changed (); + linearGradient.reset (); + radialGradient.reset (); linearGradientStart = start; linearGradientEnd = end; linearGradient = diff --git a/vstgui/lib/platform/linux/cairogradient.h b/vstgui/lib/platform/linux/cairogradient.h index d2a3fc896..785247db9 100644 --- a/vstgui/lib/platform/linux/cairogradient.h +++ b/vstgui/lib/platform/linux/cairogradient.h @@ -19,18 +19,18 @@ class Gradient : public PlatformGradientBase public: ~Gradient () noexcept override; - const PatternHandle& getLinearGradient (CPoint start, CPoint end); + const PatternHandle& getLinearGradient (CPoint start, CPoint end) const; const PatternHandle& getRadialGradient (); private: void changed () override; /* we want to calculate a normalized linear and radial gradiant */ - PatternHandle linearGradient; - PatternHandle radialGradient; + mutable PatternHandle linearGradient; + mutable PatternHandle radialGradient; - CPoint linearGradientStart; - CPoint linearGradientEnd; + mutable CPoint linearGradientStart; + mutable CPoint linearGradientEnd; }; //------------------------------------------------------------------------ diff --git a/vstgui/lib/platform/linux/cairographicscontext.cpp b/vstgui/lib/platform/linux/cairographicscontext.cpp new file mode 100644 index 000000000..ab620cc91 --- /dev/null +++ b/vstgui/lib/platform/linux/cairographicscontext.cpp @@ -0,0 +1,712 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE + +#include "cairographicscontext.h" +#include "cairobitmap.h" +#include "cairopath.h" +#include "cairogradient.h" +#include "../../crect.h" +#include "../../cgraphicstransform.h" +#include "../../ccolor.h" +#include "../../cdrawdefs.h" +#include "../../clinestyle.h" + +#include +#include + +//------------------------------------------------------------------------ +namespace VSTGUI { + +using TransformMatrix = CGraphicsTransform; + +//------------------------------------------------------------------------ +inline void checkCairoStatus (const Cairo::ContextHandle& handle) +{ +#if DEBUG + auto status = cairo_status (handle); + if (status != CAIRO_STATUS_SUCCESS) + { + auto msg = cairo_status_to_string (status); + DebugPrint ("%s\n", msg); + } +#endif +} + +//------------------------------------------------------------------------ +inline cairo_matrix_t convert (const TransformMatrix& ct) +{ + return {ct.m11, ct.m21, ct.m12, ct.m22, ct.dx, ct.dy}; +} + +//----------------------------------------------------------------------------- +struct CairoGraphicsDeviceFactory::Impl +{ + std::vector> devices; +}; + +//----------------------------------------------------------------------------- +CairoGraphicsDeviceFactory::CairoGraphicsDeviceFactory () +{ + impl = std::make_unique (); +} + +//----------------------------------------------------------------------------- +CairoGraphicsDeviceFactory::~CairoGraphicsDeviceFactory () noexcept = default; + +//----------------------------------------------------------------------------- +PlatformGraphicsDevicePtr CairoGraphicsDeviceFactory::getDeviceForScreen (ScreenInfo::Identifier screen) const +{ + if (impl->devices.empty ()) + { + // just create a dummy device as we don't really need the cairo device at the moment + impl->devices.push_back (std::make_shared (nullptr)); + } + return impl->devices.front (); +} + +//----------------------------------------------------------------------------- +PlatformGraphicsDevicePtr CairoGraphicsDeviceFactory::addDevice (cairo_device_t* device) +{ + for (auto& dev : impl->devices) + { + if (dev->get () == device) + return dev; + } + impl->devices.push_back (std::make_shared (device)); + return impl->devices.back (); +} + +//----------------------------------------------------------------------------- +struct CairoGraphicsDevice::Impl +{ + cairo_device_t* device; +}; + +//----------------------------------------------------------------------------- +CairoGraphicsDevice::CairoGraphicsDevice (cairo_device_t* device) +{ + impl = std::make_unique (); + impl->device = device; + if (device) + cairo_device_reference (device); +} + +//----------------------------------------------------------------------------- +CairoGraphicsDevice::~CairoGraphicsDevice () noexcept +{ + if (impl->device) + cairo_device_destroy (impl->device); +} + +//----------------------------------------------------------------------------- +cairo_device_t* CairoGraphicsDevice::get () const { return impl->device; } + +//----------------------------------------------------------------------------- +PlatformGraphicsDeviceContextPtr + CairoGraphicsDevice::createBitmapContext (const PlatformBitmapPtr& bitmap) const +{ + auto cairoBitmap = bitmap.cast (); + if (cairoBitmap) + { + return std::make_shared (*this, cairoBitmap->getSurface ()); + } + return nullptr; +} + +//----------------------------------------------------------------------------- +inline CPoint pixelAlign (const CGraphicsTransform& tm, const CPoint& p) +{ + auto obj = p; + tm.transform (obj); + obj.x = std::round (obj.x); + obj.y = std::round (obj.y); + tm.inverse ().transform (obj); + return obj; +} + +//----------------------------------------------------------------------------- +inline CRect pixelAlign (const CGraphicsTransform& tm, const CRect& r) +{ + auto obj = r; + tm.transform (obj); + obj.left = std::round (obj.left); + obj.right = std::round (obj.right); + obj.top = std::round (obj.top); + obj.bottom = std::round (obj.bottom); + tm.inverse ().transform (obj); + return obj; +} + +//------------------------------------------------------------------------ +struct CairoGraphicsDeviceContext::Impl +{ + const CairoGraphicsDevice& device; + Cairo::ContextHandle context; + Cairo::SurfaceHandle surface; + + Impl (const CairoGraphicsDevice& device, const Cairo::SurfaceHandle& surface) + : device (device), surface (surface) + { + context.assign (cairo_create (surface)); + } + + template + void doInContext (Proc p) + { + auto ct = state.tm; + CRect clip = state.clip; + if (clip.isEmpty ()) + return; + cairo_save (context); + cairo_rectangle (context, clip.left, clip.top, clip.getWidth (), clip.getHeight ()); + cairo_clip (context); + auto matrix = convert (ct); + cairo_set_matrix (context, &matrix); + auto antialiasMode = state.drawMode.modeIgnoringIntegralMode () == kAntiAliasing + ? CAIRO_ANTIALIAS_BEST + : CAIRO_ANTIALIAS_NONE; + cairo_set_antialias (context, antialiasMode); + p (); + checkCairoStatus (context); + cairo_restore (context); + } + + void applyLineWidthCTM () + { + auto p = calcLineTranslate (); + cairo_translate (context, p.x, p.y); + } + + CPoint calcLineTranslate () const + { + CPoint p {}; + int32_t lineWidthInt = static_cast (state.lineWidth); + if (static_cast (lineWidthInt) == state.lineWidth && lineWidthInt % 2) + p.x = p.y = 0.5; + return p; + } + + void applyLineStyle () + { + auto lineWidth = state.lineWidth; + cairo_set_line_width (context, lineWidth); + const auto& style = state.lineStyle; + if (!style.getDashLengths ().empty ()) + { + auto lengths = style.getDashLengths (); + for (auto& l : lengths) + l *= lineWidth; + cairo_set_dash (context, lengths.data (), lengths.size (), style.getDashPhase ()); + } + cairo_line_cap_t lineCap; + switch (style.getLineCap ()) + { + case CLineStyle::kLineCapButt: + { + lineCap = CAIRO_LINE_CAP_BUTT; + break; + } + case CLineStyle::kLineCapRound: + { + lineCap = CAIRO_LINE_CAP_ROUND; + break; + } + case CLineStyle::kLineCapSquare: + { + lineCap = CAIRO_LINE_CAP_SQUARE; + break; + } + } + cairo_set_line_cap (context, lineCap); + cairo_line_join_t lineJoin; + switch (style.getLineJoin ()) + { + case CLineStyle::kLineJoinBevel: + { + lineJoin = CAIRO_LINE_JOIN_BEVEL; + break; + } + case CLineStyle::kLineJoinMiter: + { + lineJoin = CAIRO_LINE_JOIN_MITER; + break; + } + case CLineStyle::kLineJoinRound: + { + lineJoin = CAIRO_LINE_JOIN_ROUND; + break; + } + } + + cairo_set_line_join (context, lineJoin); + } + + void setupSourceColor (CColor color) + { + auto alpha = color.normAlpha (); + alpha *= state.globalAlpha; + cairo_set_source_rgba (context, color.normRed (), color.normGreen (), + color.normBlue (), alpha); + checkCairoStatus (context); + } + void applyFillColor () { setupSourceColor (state.fillColor); } + void applyFrameColor () { setupSourceColor (state.frameColor); } + void applyFontColor (CColor color) { setupSourceColor (color); } + + void draw (PlatformGraphicsDrawStyle drawStyle) + { + switch (drawStyle) + { + case PlatformGraphicsDrawStyle::Stroked: + { + applyLineStyle (); + applyFrameColor (); + cairo_stroke (context); + break; + } + case PlatformGraphicsDrawStyle::Filled: + { + applyFillColor (); + cairo_fill (context); + break; + } + case PlatformGraphicsDrawStyle::FilledAndStroked: + { + applyFillColor (); + cairo_fill_preserve (context); + applyLineStyle (); + applyFrameColor (); + cairo_stroke (context); + break; + } + } + checkCairoStatus (context); + } + + struct State + { + CRect clip {}; + CLineStyle lineStyle {kLineSolid}; + CDrawMode drawMode {}; + CColor fillColor {kTransparentCColor}; + CColor frameColor {kTransparentCColor}; + CCoord lineWidth {1.}; + double globalAlpha {1.}; + TransformMatrix tm {}; + }; + State state; + std::stack stateStack; + double scaleFactor {1.}; + + PlatformGraphicsPathFactoryPtr pathFactory; +}; + +//------------------------------------------------------------------------ +CairoGraphicsDeviceContext::CairoGraphicsDeviceContext (const CairoGraphicsDevice& device, + const Cairo::SurfaceHandle& handle) +{ + impl = std::make_unique (device, handle); +} + +//------------------------------------------------------------------------ +CairoGraphicsDeviceContext::~CairoGraphicsDeviceContext () noexcept {} + +//------------------------------------------------------------------------ +const IPlatformGraphicsDevice& CairoGraphicsDeviceContext::getDevice () const +{ + return impl->device; +} + +//------------------------------------------------------------------------ +PlatformGraphicsPathFactoryPtr CairoGraphicsDeviceContext::getGraphicsPathFactory () const +{ + if (!impl->pathFactory) + impl->pathFactory = std::make_shared (impl->context); + return impl->pathFactory; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::beginDraw () const +{ + if (impl->context) + cairo_save (impl->context); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::endDraw () const +{ + if (impl->context) + cairo_restore (impl->context); + if (impl->surface) + cairo_surface_flush (impl->surface); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawLine (LinePair line) const +{ + impl->doInContext ([&] () { + impl->applyLineStyle (); + impl->applyFrameColor (); + if (impl->state.drawMode.integralMode ()) + { + CPoint start = pixelAlign (impl->state.tm, line.first); + CPoint end = pixelAlign (impl->state.tm, line.second); + impl->applyLineWidthCTM (); + cairo_move_to (impl->context, start.x, start.y); + cairo_line_to (impl->context, end.x, end.y); + } + else + { + cairo_move_to (impl->context, line.first.x, line.first.y); + cairo_line_to (impl->context, line.second.x, line.second.y); + } + cairo_stroke (impl->context); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawLines (const LineList& lines) const +{ + impl->doInContext ([&] () { + impl->applyLineStyle (); + impl->applyFrameColor (); + if (impl->state.drawMode.integralMode ()) + { + auto pt = impl->calcLineTranslate (); + for (auto& line : lines) + { + CPoint start = pixelAlign (impl->state.tm, line.first); + CPoint end = pixelAlign (impl->state.tm, line.second); + cairo_move_to (impl->context, start.x + pt.x, start.y + pt.y); + cairo_line_to (impl->context, end.x + pt.x, end.y + pt.y); + cairo_stroke (impl->context); + } + } + else + { + for (auto& line : lines) + { + cairo_move_to (impl->context, line.first.x, line.first.y); + cairo_line_to (impl->context, line.second.x, line.second.y); + cairo_stroke (impl->context); + } + } + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawPolygon (const PointList& polygonPointList, + PlatformGraphicsDrawStyle drawStyle) const +{ + vstgui_assert (polygonPointList.empty () == false); + impl->doInContext ([&] () { + bool doPixelAlign = impl->state.drawMode.integralMode (); + auto last = polygonPointList.back (); + if (doPixelAlign) + last = pixelAlign (impl->state.tm, last); + cairo_move_to (impl->context, last.x, last.y); + for (auto p : polygonPointList) + { + if (doPixelAlign) + p = pixelAlign (impl->state.tm, p); + cairo_line_to (impl->context, p.x, p.y); + } + impl->draw (drawStyle); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInContext ([&] () { + if (drawStyle != PlatformGraphicsDrawStyle::Filled) + { + rect.right -= 1.; + rect.bottom -= 1.; + } + if (impl->state.drawMode.integralMode ()) + { + rect = pixelAlign (impl->state.tm, rect); + if (drawStyle != PlatformGraphicsDrawStyle::Filled) + impl->applyLineWidthCTM (); + cairo_rectangle (impl->context, rect.left, rect.top, rect.getWidth (), + rect.getHeight ()); + } + else + { + cairo_rectangle (impl->context, rect.left + 0.5, rect.top + 0.5, rect.getWidth () - 0.5, + rect.getHeight () - 0.5); + } + impl->draw (drawStyle); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawArc (CRect rect, double startAngle1, double endAngle2, + PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInContext ([&] () { + CPoint center = rect.getCenter (); + cairo_translate (impl->context, center.x, center.y); + cairo_scale (impl->context, 2.0 / rect.getWidth (), 2.0 / rect.getHeight ()); + cairo_arc (impl->context, 0, 0, 1, startAngle1, endAngle2); + impl->draw (drawStyle); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInContext ([&] () { + CPoint center = rect.getCenter (); + cairo_translate (impl->context, center.x, center.y); + cairo_scale (impl->context, 2.0 / rect.getWidth (), 2.0 / rect.getHeight ()); + cairo_arc (impl->context, 0, 0, 1, 0, 2 * M_PI); + impl->draw (drawStyle); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawPoint (CPoint point, CColor color) const { return false; } + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, + double alpha, BitmapInterpolationQuality quality) const +{ + auto cairoBitmap = dynamic_cast (&bitmap); + if (!cairoBitmap) + return false; + impl->doInContext ([&] () { + cairo_translate (impl->context, dest.left, dest.top); + cairo_rectangle (impl->context, 0, 0, dest.getWidth (), dest.getHeight ()); + cairo_clip (impl->context); + + // Setup a pattern for scaling bitmaps and take it as source afterwards. + auto pattern = cairo_pattern_create_for_surface (cairoBitmap->getSurface ()); + cairo_matrix_t matrix; + cairo_pattern_get_matrix (pattern, &matrix); + cairo_matrix_init_scale (&matrix, cairoBitmap->getScaleFactor (), + cairoBitmap->getScaleFactor ()); + cairo_matrix_translate (&matrix, offset.x, offset.y); + cairo_pattern_set_matrix (pattern, &matrix); + cairo_set_source (impl->context, pattern); + + cairo_rectangle (impl->context, -offset.x, -offset.y, dest.getWidth () + offset.x, + dest.getHeight () + offset.y); + alpha *= impl->state.globalAlpha; + if (alpha != 1.f) + { + cairo_paint_with_alpha (impl->context, alpha); + } + else + { + cairo_fill (impl->context); + } + cairo_pattern_destroy (pattern); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::clearRect (CRect rect) const +{ + impl->doInContext ([&] () { + cairo_set_operator (impl->context, CAIRO_OPERATOR_CLEAR); + cairo_rectangle (impl->context, rect.left, rect.top, rect.getWidth (), rect.getHeight ()); + cairo_fill (impl->context); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::drawGraphicsPath (IPlatformGraphicsPath& path, + PlatformGraphicsPathDrawMode mode, + TransformMatrix* transformation) const +{ + auto cairoPath = dynamic_cast (&path); + if (!cairoPath) + return false; + impl->doInContext ([&] () { + std::unique_ptr alignedPath; + if (impl->state.drawMode.integralMode ()) + { + alignedPath = cairoPath->copyPixelAlign ([&] (CPoint p) { + p = pixelAlign (impl->state.tm, p); + return p; + }); + } + auto p = alignedPath ? alignedPath->getCairoPath () : cairoPath->getCairoPath (); + if (transformation) + { + cairo_matrix_t currentMatrix; + cairo_matrix_t resultMatrix; + auto matrix = convert (*transformation); + cairo_get_matrix (impl->context, ¤tMatrix); + cairo_matrix_multiply (&resultMatrix, &matrix, ¤tMatrix); + cairo_set_matrix (impl->context, &resultMatrix); + } + cairo_append_path (impl->context, p); + switch (mode) + { + case PlatformGraphicsPathDrawMode::Filled: + { + impl->applyFillColor (); + cairo_fill (impl->context); + break; + } + case PlatformGraphicsPathDrawMode::FilledEvenOdd: + { + impl->applyFillColor (); + cairo_set_fill_rule (impl->context, CAIRO_FILL_RULE_EVEN_ODD); + cairo_fill (impl->context); + break; + } + case PlatformGraphicsPathDrawMode::Stroked: + { + impl->applyLineStyle (); + impl->applyFrameColor (); + cairo_stroke (impl->context); + break; + } + } + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::fillLinearGradient (IPlatformGraphicsPath& path, + const IPlatformGradient& gradient, + CPoint startPoint, CPoint endPoint, + bool evenOdd, + TransformMatrix* transformation) const +{ + auto cairoPath = dynamic_cast (&path); + if (!cairoPath) + return false; + auto cairoGradient = dynamic_cast (&gradient); + if (!cairoGradient) + return false; + impl->doInContext ([&] () { + std::unique_ptr alignedPath; + if (impl->state.drawMode.integralMode ()) + { + alignedPath = cairoPath->copyPixelAlign ([&] (CPoint p) { + p = pixelAlign (impl->state.tm, p); + return p; + }); + } + auto p = alignedPath ? alignedPath->getCairoPath () : cairoPath->getCairoPath (); + cairo_append_path (impl->context, p); + cairo_set_source (impl->context, cairoGradient->getLinearGradient (startPoint, endPoint)); + if (evenOdd) + cairo_set_fill_rule (impl->context, CAIRO_FILL_RULE_EVEN_ODD); + cairo_fill (impl->context); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CairoGraphicsDeviceContext::fillRadialGradient (IPlatformGraphicsPath& path, + const IPlatformGradient& gradient, + CPoint center, CCoord radius, + CPoint originOffset, bool evenOdd, + TransformMatrix* transformation) const +{ + auto cairoPath = dynamic_cast (&path); + if (!cairoPath) + return false; + // TODO: Implementation + return false; +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::saveGlobalState () const +{ + cairo_save (impl->context); + impl->stateStack.push (impl->state); +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::restoreGlobalState () const +{ + vstgui_assert (impl->stateStack.empty () == false, + "Unbalanced calls to saveGlobalState and restoreGlobalState"); +#if NDEBUG + if (impl->stateStack.empty ()) + return; +#endif + cairo_restore (impl->context); + impl->state = impl->stateStack.top (); + impl->stateStack.pop (); +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setLineStyle (const CLineStyle& style) const +{ + impl->state.lineStyle = style; +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setLineWidth (CCoord width) const +{ + + impl->state.lineWidth = width; +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setDrawMode (CDrawMode mode) const { impl->state.drawMode = mode; } + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setClipRect (CRect clip) const { impl->state.clip = clip; } + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setFillColor (CColor color) const +{ + impl->state.fillColor = color; +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setFrameColor (CColor color) const +{ + impl->state.frameColor = color; +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setGlobalAlpha (double newAlpha) const +{ + impl->state.globalAlpha = newAlpha; +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::setTransformMatrix (const TransformMatrix& tm) const +{ + impl->state.tm = tm; +} + +//------------------------------------------------------------------------ +const IPlatformGraphicsDeviceContextBitmapExt* CairoGraphicsDeviceContext::asBitmapExt () const +{ + return nullptr; +} + +//------------------------------------------------------------------------ +void CairoGraphicsDeviceContext::drawPangoLayout (void* layout, CPoint pos, CColor color) const +{ + impl->doInContext ([&] () { + impl->applyFontColor (color); + cairo_move_to (impl->context, pos.x, pos.y); + pango_cairo_show_layout (impl->context, reinterpret_cast (layout)); + }); +} + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/linux/cairographicscontext.h b/vstgui/lib/platform/linux/cairographicscontext.h new file mode 100644 index 000000000..9c65d64ec --- /dev/null +++ b/vstgui/lib/platform/linux/cairographicscontext.h @@ -0,0 +1,107 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE + +#pragma once + +#include "../iplatformgraphicsdevice.h" + +#include "cairoutils.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { + +class CairoGraphicsDevice; + +//------------------------------------------------------------------------ +class CairoGraphicsDeviceContext : public IPlatformGraphicsDeviceContext +{ +public: + CairoGraphicsDeviceContext (const CairoGraphicsDevice& device, + const Cairo::SurfaceHandle& handle); + ~CairoGraphicsDeviceContext () noexcept; + + const IPlatformGraphicsDevice& getDevice () const override; + PlatformGraphicsPathFactoryPtr getGraphicsPathFactory () const override; + + bool beginDraw () const override; + bool endDraw () const override; + // draw commands + bool drawLine (LinePair line) const override; + bool drawLines (const LineList& lines) const override; + bool drawPolygon (const PointList& polygonPointList, + PlatformGraphicsDrawStyle drawStyle) const override; + bool drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override; + bool drawArc (CRect rect, double startAngle1, double endAngle2, + PlatformGraphicsDrawStyle drawStyle) const override; + bool drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override; + bool drawPoint (CPoint point, CColor color) const override; + bool drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, double alpha, + BitmapInterpolationQuality quality) const override; + bool clearRect (CRect rect) const override; + bool drawGraphicsPath (IPlatformGraphicsPath& path, PlatformGraphicsPathDrawMode mode, + TransformMatrix* transformation) const override; + bool fillLinearGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint startPoint, CPoint endPoint, bool evenOdd, + TransformMatrix* transformation) const override; + bool fillRadialGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint center, CCoord radius, CPoint originOffset, bool evenOdd, + TransformMatrix* transformation) const override; + // state + void saveGlobalState () const override; + void restoreGlobalState () const override; + void setLineStyle (const CLineStyle& style) const override; + void setLineWidth (CCoord width) const override; + void setDrawMode (CDrawMode mode) const override; + void setClipRect (CRect clip) const override; + void setFillColor (CColor color) const override; + void setFrameColor (CColor color) const override; + void setGlobalAlpha (double newAlpha) const override; + void setTransformMatrix (const TransformMatrix& tm) const override; + + // extension + const IPlatformGraphicsDeviceContextBitmapExt* asBitmapExt () const override; + + // private + void drawPangoLayout (void* layout, CPoint pos, CColor color) const; + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +class CairoGraphicsDevice : public IPlatformGraphicsDevice +{ +public: + CairoGraphicsDevice (cairo_device_t* device); + ~CairoGraphicsDevice () noexcept; + + PlatformGraphicsDeviceContextPtr + createBitmapContext (const PlatformBitmapPtr& bitmap) const override; + + cairo_device_t* get () const; + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +class CairoGraphicsDeviceFactory : public IPlatformGraphicsDeviceFactory +{ +public: + CairoGraphicsDeviceFactory (); + ~CairoGraphicsDeviceFactory () noexcept; + + PlatformGraphicsDevicePtr getDeviceForScreen (ScreenInfo::Identifier screen) const override; + + PlatformGraphicsDevicePtr addDevice (cairo_device_t* device); + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/linux/cairopath.cpp b/vstgui/lib/platform/linux/cairopath.cpp index e7a3d6535..3dae6ab5f 100644 --- a/vstgui/lib/platform/linux/cairopath.cpp +++ b/vstgui/lib/platform/linux/cairopath.cpp @@ -4,7 +4,6 @@ #include "../../cgradient.h" #include "../../cgraphicstransform.h" -#include "cairocontext.h" #include "cairopath.h" //------------------------------------------------------------------------ @@ -116,16 +115,16 @@ void GraphicsPath::closeSubpath () } //------------------------------------------------------------------------ -std::unique_ptr GraphicsPath::copyPixelAlign (const CGraphicsTransform& tm) +std::unique_ptr GraphicsPath::copyPixelAlign (const std::function& pixelAlignFunc) { auto result = std::make_unique (context); cairo_append_path (context, path); result->finishBuilding (); auto rpath = result->path; - auto align = [] (_cairo_path_data_t* data, int index, const CGraphicsTransform& tm) { + auto align = [&] (_cairo_path_data_t* data, int index) { CPoint input (data[index].point.x, data[index].point.y); - auto output = Cairo::pixelAlign (tm, input); + auto output = pixelAlignFunc (input); data[index].point.x = output.x; data[index].point.y = output.y; }; @@ -136,19 +135,19 @@ std::unique_ptr GraphicsPath::copyPixelAlign (const CGraphicsTrans { case CAIRO_PATH_MOVE_TO: { - align (data, 1, tm); + align (data, 1); break; } case CAIRO_PATH_LINE_TO: { - align (data, 1, tm); + align (data, 1); break; } case CAIRO_PATH_CURVE_TO: { - align (data, 1, tm); - align (data, 2, tm); - align (data, 3, tm); + align (data, 1); + align (data, 2); + align (data, 3); break; } case CAIRO_PATH_CLOSE_PATH: { break; diff --git a/vstgui/lib/platform/linux/cairopath.h b/vstgui/lib/platform/linux/cairopath.h index 8aede58a7..25477f957 100644 --- a/vstgui/lib/platform/linux/cairopath.h +++ b/vstgui/lib/platform/linux/cairopath.h @@ -34,7 +34,8 @@ class GraphicsPath : public IPlatformGraphicsPath ~GraphicsPath () noexcept; cairo_path_t* getCairoPath () const { return path; } - std::unique_ptr copyPixelAlign (const CGraphicsTransform& tm); + std::unique_ptr + copyPixelAlign (const std::function& pixelAlignFunc); // IPlatformGraphicsPath void addArc (const CRect& rect, double startAngle, double endAngle, bool clockwise) override; diff --git a/vstgui/lib/platform/linux/linuxfactory.cpp b/vstgui/lib/platform/linux/linuxfactory.cpp index 202ed91a7..9bbae8e40 100644 --- a/vstgui/lib/platform/linux/linuxfactory.cpp +++ b/vstgui/lib/platform/linux/linuxfactory.cpp @@ -5,11 +5,12 @@ #include "cairobitmap.h" #include "cairofont.h" #include "cairogradient.h" -#include "cairocontext.h" +#include "cairographicscontext.h" #include "x11frame.h" #include "../iplatformframecallback.h" #include "../common/fileresourceinputstream.h" #include "../iplatformresourceinputstream.h" +#include "../iplatformgraphicsdevice.h" #include "linuxstring.h" #include "x11timer.h" #include "x11fileselector.h" @@ -28,6 +29,7 @@ namespace VSTGUI { struct LinuxFactory::Impl { std::string resPath; + std::unique_ptr graphicsDeviceFactory {std::make_unique ()}; void setupResPath (void* handle) { @@ -187,19 +189,6 @@ auto LinuxFactory::getClipboard () const noexcept -> DataPackagePtr return nullptr; } -//------------------------------------------------------------------------ -auto LinuxFactory::createOffscreenContext (const CPoint& size, double scaleFactor) const noexcept - -> COffscreenContextPtr -{ - auto bitmap = new Cairo::Bitmap (size * scaleFactor); - bitmap->setScaleFactor (scaleFactor); - auto context = owned (new Cairo::Context (bitmap)); - bitmap->forget (); - if (context->valid ()) - return context; - return nullptr; -} - //----------------------------------------------------------------------------- PlatformGradientPtr LinuxFactory::createGradient () const noexcept { @@ -214,6 +203,12 @@ PlatformFileSelectorPtr LinuxFactory::createFileSelector (PlatformFileSelectorSt return X11::createFileSelector (style, x11Frame); } +//----------------------------------------------------------------------------- +const IPlatformGraphicsDeviceFactory& LinuxFactory::getGraphicsDeviceFactory () const noexcept +{ + return *impl->graphicsDeviceFactory.get (); +} + //----------------------------------------------------------------------------- const LinuxFactory* LinuxFactory::asLinuxFactory () const noexcept { @@ -232,5 +227,11 @@ const Win32Factory* LinuxFactory::asWin32Factory () const noexcept return nullptr; } +//----------------------------------------------------------------------------- +CairoGraphicsDeviceFactory& LinuxFactory::getCairoGraphicsDeviceFactory () const noexcept +{ + return *impl->graphicsDeviceFactory.get (); +} + //----------------------------------------------------------------------------- } // VSTGUI diff --git a/vstgui/lib/platform/linux/linuxfactory.h b/vstgui/lib/platform/linux/linuxfactory.h index 306085c51..a2784c2cf 100644 --- a/vstgui/lib/platform/linux/linuxfactory.h +++ b/vstgui/lib/platform/linux/linuxfactory.h @@ -8,6 +8,7 @@ //----------------------------------------------------------------------------- namespace VSTGUI { +class CairoGraphicsDeviceFactory; //----------------------------------------------------------------------------- class LinuxFactory final : public IPlatformFactory @@ -108,14 +109,6 @@ class LinuxFactory final : public IPlatformFactory */ DataPackagePtr getClipboard () const noexcept final; - /** create an offscreen draw device - * @param size the size of the bitmap where the offscreen renders to - * @param scaleFactor the scale factor for drawing - * @return an offscreen context object or nullptr on failure - */ - COffscreenContextPtr createOffscreenContext (const CPoint& size, - double scaleFactor = 1.) const noexcept final; - /** Create a platform gradient object * @return platform gradient object or nullptr on failure */ @@ -129,10 +122,17 @@ class LinuxFactory final : public IPlatformFactory PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style, IPlatformFrame* frame) const noexcept final; + /** Get the graphics device factory + * + * @return platform graphics device factory + */ + const IPlatformGraphicsDeviceFactory& getGraphicsDeviceFactory () const noexcept final; + const LinuxFactory* asLinuxFactory () const noexcept final; const MacFactory* asMacFactory () const noexcept final; const Win32Factory* asWin32Factory () const noexcept final; + CairoGraphicsDeviceFactory& getCairoGraphicsDeviceFactory () const noexcept; private: struct Impl; std::unique_ptr impl; diff --git a/vstgui/lib/platform/linux/x11frame.cpp b/vstgui/lib/platform/linux/x11frame.cpp index 9975194d0..8eb331cb0 100644 --- a/vstgui/lib/platform/linux/x11frame.cpp +++ b/vstgui/lib/platform/linux/x11frame.cpp @@ -19,7 +19,8 @@ #include "../common/generictextedit.h" #include "../common/genericoptionmenu.h" #include "cairobitmap.h" -#include "cairocontext.h" +#include "linuxfactory.h" +#include "cairographicscontext.h" #include "x11platform.h" #include "x11utils.h" #include @@ -171,8 +172,10 @@ struct DrawHandler window.getID (), window.getVisual (), window.getSize ().x, window.getSize ().y); windowSurface.assign (s); + device = + getPlatformFactory ().asLinuxFactory ()->getCairoGraphicsDeviceFactory ().addDevice ( + cairo_surface_get_device (s)); onSizeChanged (window.getSize ()); - RunLoop::instance ().setDevice (cairo_surface_get_device (s)); } void onSizeChanged (const CPoint& size) @@ -180,45 +183,40 @@ struct DrawHandler cairo_xcb_surface_set_size (windowSurface, size.x, size.y); backBuffer = Cairo::SurfaceHandle (cairo_surface_create_similar ( windowSurface, CAIRO_CONTENT_COLOR_ALPHA, size.x, size.y)); - CRect r; - r.setSize (size); - drawContext = makeOwned (r, backBuffer); + backBufferSize.setSize (size); + auto cairoDevice = std::static_pointer_cast (device); + drawContext = std::make_shared (*cairoDevice, backBuffer); } - template - void draw (const RectList& dirtyRects, Proc proc) + void draw (const CInvalidRectList& dirtyRects, IPlatformFrameCallback* frame) { - CRect copyRect; drawContext->beginDraw (); - for (auto rect : dirtyRects) - { - drawContext->setClipRect (rect); - drawContext->saveGlobalState (); - proc (drawContext, rect); - drawContext->restoreGlobalState (); - if (copyRect.isEmpty ()) - copyRect = rect; - else - copyRect.unite (rect); - } + frame->platformDrawRects (drawContext, 1, dirtyRects.data ()); drawContext->endDraw (); - blitBackbufferToWindow (copyRect); + + blitBackbufferToWindow (dirtyRects); xcb_flush (RunLoop::instance ().getXcbConnection ()); } private: Cairo::SurfaceHandle windowSurface; Cairo::SurfaceHandle backBuffer; - SharedPointer drawContext; + CRect backBufferSize; + std::shared_ptr drawContext; + PlatformGraphicsDevicePtr device; - void blitBackbufferToWindow (const CRect& rect) + void blitBackbufferToWindow (const CInvalidRectList& rects) { Cairo::ContextHandle windowContext (cairo_create (windowSurface)); - cairo_rectangle (windowContext, rect.left, rect.top, rect.getWidth (), rect.getHeight ()); - cairo_clip (windowContext); cairo_set_source_surface (windowContext, backBuffer, 0, 0); - cairo_rectangle (windowContext, rect.left, rect.top, rect.getWidth (), rect.getHeight ()); - cairo_fill (windowContext); + for (auto rect : rects) + { + cairo_rectangle (windowContext, rect.left, rect.top, rect.getWidth (), + rect.getHeight ()); + cairo_clip_preserve (windowContext); + cairo_fill (windowContext); + cairo_reset_clip (windowContext); + } cairo_surface_flush (windowSurface); } }; @@ -366,9 +364,7 @@ struct Frame::Impl : IFrameEventHandler //------------------------------------------------------------------------ void redraw () { - drawHandler.draw (dirtyRects, [&] (CDrawContext* context, const CRect& rect) { - frame->platformDrawRect (context, rect); - }); + drawHandler.draw (dirtyRects, frame); dirtyRects.clear (); } diff --git a/vstgui/lib/platform/mac/caviewlayer.h b/vstgui/lib/platform/mac/caviewlayer.h index b1eaef1f5..d83692e99 100644 --- a/vstgui/lib/platform/mac/caviewlayer.h +++ b/vstgui/lib/platform/mac/caviewlayer.h @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -9,11 +9,21 @@ #if MAC_COCOA #include "../platform_macos.h" +#include namespace VSTGUI { - + +//------------------------------------------------------------------------ +struct ICAViewLayerPrivate +{ + virtual ~ICAViewLayerPrivate () = default; + virtual void drawLayer (void* cgContext) = 0; +}; + //----------------------------------------------------------------------------- -class CAViewLayer : public IPlatformViewLayer, public ICocoaViewLayer +class CAViewLayer : public IPlatformViewLayer, + public ICocoaViewLayer, + private ICAViewLayerPrivate //----------------------------------------------------------------------------- { public: @@ -21,18 +31,21 @@ class CAViewLayer : public IPlatformViewLayer, public ICocoaViewLayer ~CAViewLayer () noexcept override; bool init (IPlatformViewLayerDelegate* drawDelegate); - + void invalidRect (const CRect& size) override; void setSize (const CRect& size) override; void setZIndex (uint32_t zIndex) override; void setAlpha (float alpha) override; - void draw (CDrawContext* context, const CRect& updateRect) override; void onScaleFactorChanged (double newScaleFactor) override; CALayer* getCALayer () const override { return layer; } + //----------------------------------------------------------------------------- -protected: - CALayer* layer; +private: + void drawLayer (void* cgContext) final; + + CALayer* layer {nullptr}; + IPlatformViewLayerDelegate* drawDelegate {nullptr}; }; } // VSTGUI diff --git a/vstgui/lib/platform/mac/caviewlayer.mm b/vstgui/lib/platform/mac/caviewlayer.mm index b2b754b08..3163d16c6 100644 --- a/vstgui/lib/platform/mac/caviewlayer.mm +++ b/vstgui/lib/platform/mac/caviewlayer.mm @@ -6,7 +6,7 @@ #if MAC_COCOA -#import "cgdrawcontext.h" +#import "coregraphicsdevicecontext.h" #import "macglobals.h" #import @@ -21,7 +21,7 @@ @interface VSTGUI_CALayer : CALayer //----------------------------------------------------------------------------- { - VSTGUI::IPlatformViewLayerDelegate* _viewLayerDelegate; + VSTGUI::ICAViewLayerPrivate* _viewLayer; } @end @@ -48,20 +48,16 @@ - (id)init } //----------------------------------------------------------------------------- -- (void)setDrawDelegate:(VSTGUI::IPlatformViewLayerDelegate*)viewLayerDelegate +- (void)setCAViewLayer:(VSTGUI::ICAViewLayerPrivate*)viewLayer { - _viewLayerDelegate = viewLayerDelegate; + _viewLayer = viewLayer; } //----------------------------------------------------------------------------- - (void)drawInContext:(CGContextRef)ctx { - if (_viewLayerDelegate) - { - CGRect dirtyRect = CGContextGetClipBoundingBox (ctx); - VSTGUI::CGDrawContext drawContext (ctx, VSTGUI::CRectFromCGRect (self.bounds)); - _viewLayerDelegate->drawViewLayer (&drawContext, VSTGUI::CRectFromCGRect (dirtyRect)); - } + if (_viewLayer) + _viewLayer->drawLayer (ctx); } @end @@ -72,13 +68,13 @@ - (void)drawInContext:(CGContextRef)ctx //----------------------------------------------------------------------------- @interface VSTGUI_CALayer : CALayer //----------------------------------------------------------------------------- -- (void)setDrawDelegate:(VSTGUI::IPlatformViewLayerDelegate*)viewLayerDelegate; +- (void)setCAViewLayer:(VSTGUI::ICAViewLayerPrivate*)viewLayer; @end //----------------------------------------------------------------------------- struct VSTGUI_macOS_CALayer : VSTGUI::RuntimeObjCClass { - static constexpr const auto viewLayerDelegateVarName = "_viewLayerDelegate"; + static constexpr const auto viewLayerVarName = "_viewLayer"; //----------------------------------------------------------------------------- static Class CreateClass () @@ -87,9 +83,9 @@ static Class CreateClass () .init ("VSTGUI_CALayer", [CALayer class]) .addMethod (@selector (init), Init) .addMethod (@selector (actionForKey:), ActionForKey) - .addMethod (@selector (setDrawDelegate:), SetDrawDelegate) + .addMethod (@selector (setCAViewLayer:), SetCAViewLayer) .addMethod (@selector (drawInContext:), DrawInContext) - .addIvar (viewLayerDelegateVarName) + .addIvar (viewLayerVarName) .finalize (); } @@ -108,12 +104,11 @@ static id Init (id self, SEL _cmd) static id ActionForKey (id self, SEL _cmd, NSString* event) { return nil; } //----------------------------------------------------------------------------- - static void SetDrawDelegate (id self, SEL _cmd, VSTGUI::IPlatformViewLayerDelegate* delegate) + static void SetCAViewLayer (id self, SEL _cmd, VSTGUI::CAViewLayer* viewLayer) { using namespace VSTGUI; - if (auto var = makeInstance (self).getVariable ( - viewLayerDelegateVarName)) - var->set (delegate); + if (auto var = makeInstance (self).getVariable (viewLayerVarName)) + var->set (viewLayer); } //----------------------------------------------------------------------------- @@ -121,34 +116,11 @@ static void DrawInContext (id self, SEL _cmd, CGContextRef ctx) { using namespace VSTGUI; - if (auto var = makeInstance (self).getVariable ( - viewLayerDelegateVarName); + if (auto var = + makeInstance (self).getVariable (viewLayerVarName); var.has_value ()) { - static bool visualizeLayer = false; - if (visualizeLayer) - CGContextClearRect (ctx, [self bounds]); - - CGRect dirtyRect = CGContextGetClipBoundingBox (ctx); - if ([self contentsAreFlipped] == [self isGeometryFlipped]) - { - CGContextScaleCTM (ctx, 1, -1); - CGContextTranslateCTM (ctx, 0, -[self bounds].size.height); - dirtyRect.origin.y = - (-dirtyRect.origin.y - dirtyRect.size.height) + [self bounds].size.height; - } - CGContextSaveGState (ctx); - CGDrawContext drawContext (ctx, CRectFromCGRect ([(CALayer*)self bounds])); - var->get ()->drawViewLayer (&drawContext, CRectFromCGRect (dirtyRect)); - CGContextRestoreGState (ctx); - - if (visualizeLayer) - { - CGContextSetRGBFillColor (ctx, 1., 0., 0., 0.3); - CGContextFillRect (ctx, [self bounds]); - CGContextSetRGBFillColor (ctx, 0., 1., 0., 0.3); - CGContextFillRect (ctx, dirtyRect); - } + var->get ()->drawLayer (ctx); } } }; @@ -159,7 +131,6 @@ static void DrawInContext (id self, SEL _cmd, CGContextRef ctx) //----------------------------------------------------------------------------- CAViewLayer::CAViewLayer (CALayer* parent) -: layer (nullptr) { #if !TARGET_OS_IPHONE layer = [VSTGUI_macOS_CALayer::alloc () init]; @@ -168,6 +139,7 @@ static void DrawInContext (id self, SEL _cmd, CGContextRef ctx) #endif [layer setContentsScale:parent.contentsScale]; [parent addSublayer:layer]; + [(id)layer setCAViewLayer:this]; } //----------------------------------------------------------------------------- @@ -183,9 +155,9 @@ static void DrawInContext (id self, SEL _cmd, CGContextRef ctx) } //----------------------------------------------------------------------------- -bool CAViewLayer::init (IPlatformViewLayerDelegate* drawDelegate) +bool CAViewLayer::init (IPlatformViewLayerDelegate* delegate) { - [static_cast (layer) setDrawDelegate:drawDelegate]; + drawDelegate = delegate; return true; } @@ -233,15 +205,58 @@ static void DrawInContext (id self, SEL _cmd, CGContextRef ctx) } //----------------------------------------------------------------------------- -void CAViewLayer::draw (CDrawContext* context, const CRect& updateRect) +void CAViewLayer::onScaleFactorChanged (double newScaleFactor) { + if (layer) + layer.contentsScale = newScaleFactor; } //----------------------------------------------------------------------------- -void CAViewLayer::onScaleFactorChanged (double newScaleFactor) +void CAViewLayer::drawLayer (void* cgContext) { - if (layer) - layer.contentsScale = newScaleFactor; + CGContextRef ctx = reinterpret_cast (cgContext); + +#if DEBUG + static bool visualizeLayer = false; + if (visualizeLayer) + CGContextClearRect (ctx, [layer bounds]); +#endif + + CGRect dirtyRect = CGContextGetClipBoundingBox (ctx); + if ([layer contentsAreFlipped] == [layer isGeometryFlipped]) + { + CGContextScaleCTM (ctx, 1, -1); + CGContextTranslateCTM (ctx, 0, -[layer bounds].size.height); + dirtyRect.origin.y = + (-dirtyRect.origin.y - dirtyRect.size.height) + [layer bounds].size.height; + } + CGContextSaveGState (ctx); + + auto device = getPlatformFactory ().getGraphicsDeviceFactory ().getDeviceForScreen ( + DefaultScreenIdentifier); + if (!device) + return; + auto cgDevice = std::static_pointer_cast (device); + if (auto deviceContext = + std::make_shared (*cgDevice.get (), cgContext)) + { + deviceContext->beginDraw (); + drawDelegate->drawViewLayerRects (deviceContext, layer.contentsScale, + {1, CRectFromCGRect (dirtyRect)}); + deviceContext->endDraw (); + } + + CGContextRestoreGState (ctx); + +#if DEBUG + if (visualizeLayer) + { + CGContextSetRGBFillColor (ctx, 1., 0., 0., 0.3); + CGContextFillRect (ctx, [layer bounds]); + CGContextSetRGBFillColor (ctx, 0., 1., 0., 0.3); + CGContextFillRect (ctx, dirtyRect); + } +#endif } } // VSTGUI diff --git a/vstgui/lib/platform/mac/cfontmac.h b/vstgui/lib/platform/mac/cfontmac.h index bb86bcb8f..1d2f43dab 100644 --- a/vstgui/lib/platform/mac/cfontmac.h +++ b/vstgui/lib/platform/mac/cfontmac.h @@ -40,11 +40,14 @@ class CoreTextFont : public IPlatformFont, public IFontPainter protected: ~CoreTextFont () noexcept override; - void drawString (CDrawContext* context, IPlatformString* string, const CPoint& p, bool antialias = true) const override; - CCoord getStringWidth (CDrawContext* context, IPlatformString* string, bool antialias = true) const override; + void drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, + const CPoint& p, const CColor& color, bool antialias = true) const override; + CCoord getStringWidth (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, + bool antialias = true) const override; CFDictionaryRef getStringAttributes (const CGColorRef color = nullptr) const; - CTLineRef createCTLine (CDrawContext* context, MacString* macString) const; + CTLineRef createCTLine (const PlatformGraphicsDeviceContextPtr& context, MacString* macString, + const CColor& color) const; CTFontRef fontRef; int32_t style; diff --git a/vstgui/lib/platform/mac/cfontmac.mm b/vstgui/lib/platform/mac/cfontmac.mm index 0d8f0757f..90ebe472f 100644 --- a/vstgui/lib/platform/mac/cfontmac.mm +++ b/vstgui/lib/platform/mac/cfontmac.mm @@ -3,11 +3,10 @@ // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE #import "cfontmac.h" -#import "../../cdrawcontext.h" #if MAC #import "macstring.h" -#import "cgdrawcontext.h" +#import "coregraphicsdevicecontext.h" #import "macglobals.h" #if TARGET_OS_IPHONE #import @@ -125,7 +124,8 @@ void getUrlsForType (CFStringRef fontType, CFMutableArrayRef& array) //----------------------------------------------------------------------------- static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicTraits trait) { - CTFontRef traitsFontRef = CTFontCreateCopyWithSymbolicTraits (fontRef, CTFontGetSize (fontRef), nullptr, trait, trait); + auto traitsFontRef = CTFontCreateCopyWithSymbolicTraits (fontRef, CTFontGetSize (fontRef), + nullptr, trait, trait); if (traitsFontRef) { CFRelease (fontRef); @@ -134,7 +134,8 @@ static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicT else if (trait == kCTFontItalicTrait) { CGAffineTransform transform = { 1, 0, -0.5, 1, 0, 0 }; - traitsFontRef = CTFontCreateCopyWithAttributes (fontRef, CTFontGetSize (fontRef), &transform, nullptr); + traitsFontRef = + CTFontCreateCopyWithAttributes (fontRef, CTFontGetSize (fontRef), &transform, nullptr); if (traitsFontRef) { CFRelease (fontRef); @@ -162,7 +163,9 @@ static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicT { if (@available (macOS 10.10, *)) { - CFMutableDictionaryRef attributes = CFDictionaryCreateMutable (kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + auto attributes = + CFDictionaryCreateMutable (kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue (attributes, kCTFontFamilyNameAttribute, fontNameRef); CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes (attributes); fontRef = CTFontCreateWithFontDescriptor (descriptor, static_cast (size), nullptr); @@ -226,7 +229,9 @@ static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicT { if (stringAttributes == nullptr) { - stringAttributes = CFDictionaryCreateMutable (kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + stringAttributes = + CFDictionaryCreateMutable (kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue (stringAttributes, kCTFontAttributeName, fontRef); } if (color) @@ -237,17 +242,14 @@ static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicT } //----------------------------------------------------------------------------- -CTLineRef CoreTextFont::createCTLine (CDrawContext* context, MacString* macString) const +CTLineRef CoreTextFont::createCTLine (const PlatformGraphicsDeviceContextPtr& context, + MacString* macString, const CColor& color) const { - CColor fontColor = context ? context->getFontColor () : kBlackCColor; - if (context) + if (macString->getCTLineFontRef () == this && macString->getCTLineColor () == color) { - if (macString->getCTLineFontRef () == this && macString->getCTLineColor () == fontColor) - { - CTLineRef line = macString->getCTLine (); - CFRetain (line); - return line; - } + CTLineRef line = macString->getCTLine (); + CFRetain (line); + return line; } CFStringRef cfStr = macString->getCFString (); if (cfStr == nullptr) @@ -259,18 +261,19 @@ static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicT } CGColorRef cgColorRef = nullptr; - if (fontColor != lastColor) + if (color != lastColor) { - cgColorRef = getCGColor (fontColor); - lastColor = fontColor; + cgColorRef = getCGColor (color); + lastColor = color; } - CFAttributedStringRef attrStr = CFAttributedStringCreate (kCFAllocatorDefault, cfStr, getStringAttributes (cgColorRef)); - if (attrStr) + + if (auto attrStr = + CFAttributedStringCreate (kCFAllocatorDefault, cfStr, getStringAttributes (cgColorRef))) { CTLineRef line = CTLineCreateWithAttributedString (attrStr); if (context && line) { - macString->setCTLine (line, this, fontColor); + macString->setCTLine (line, this, color); } CFRelease (attrStr); return line; @@ -280,70 +283,38 @@ static CTFontRef CoreTextCreateTraitsVariant (CTFontRef fontRef, CTFontSymbolicT } //----------------------------------------------------------------------------- -void CoreTextFont::drawString (CDrawContext* context, IPlatformString* string, const CPoint& point, bool antialias) const +void CoreTextFont::drawString (const PlatformGraphicsDeviceContextPtr& context, + IPlatformString* string, const CPoint& point, const CColor& color, + bool antialias) const { MacString* macString = dynamic_cast (string); if (macString == nullptr) return; - CTLineRef line = createCTLine (context, macString); - if (line) - { - bool integralMode = context->getDrawMode ().integralMode (); - CGDrawContext* cgDrawContext = dynamic_cast (context); - CGContextRef cgContext = cgDrawContext ? cgDrawContext->beginCGContext (true, integralMode) : nullptr; - if (cgContext) - { - CGPoint cgPoint = CGPointFromCPoint (point); - if (integralMode) - cgPoint = cgDrawContext->pixelAlligned (cgPoint); - CGContextSetShouldAntialias (cgContext, antialias); - CGContextSetShouldSmoothFonts (cgContext, true); - CGContextSetShouldSubpixelPositionFonts (cgContext, true); - CGContextSetShouldSubpixelQuantizeFonts (cgContext, true); - CGContextSetTextPosition (cgContext, static_cast (point.x), cgPoint.y); - CTLineDraw (line, cgContext); - if (style & kUnderlineFace) - { - CGColorRef cgColorRef = getCGColor (context->getFontColor ()); - CGFloat underlineOffset = CTFontGetUnderlinePosition (fontRef) - 1.f; - CGFloat underlineThickness = CTFontGetUnderlineThickness (fontRef); - CGContextSetStrokeColorWithColor (cgContext, cgColorRef); - CGContextSetLineWidth (cgContext, underlineThickness); - cgPoint = CGContextGetTextPosition (cgContext); - CGContextBeginPath (cgContext); - CGContextMoveToPoint (cgContext, static_cast (point.x), cgPoint.y - underlineOffset); - CGContextAddLineToPoint (cgContext, cgPoint.x, cgPoint.y - underlineOffset); - CGContextDrawPath (cgContext, kCGPathStroke); - } - if (style & kStrikethroughFace) - { - CGColorRef cgColorRef = getCGColor (context->getFontColor ()); - CGFloat underlineThickness = CTFontGetUnderlineThickness (fontRef); - CGFloat offset = CTFontGetXHeight (fontRef) * 0.5f; - CGContextSetStrokeColorWithColor (cgContext, cgColorRef); - CGContextSetLineWidth (cgContext, underlineThickness); - cgPoint = CGContextGetTextPosition (cgContext); - CGContextBeginPath (cgContext); - CGContextMoveToPoint (cgContext, static_cast (point.x), cgPoint.y - offset); - CGContextAddLineToPoint (cgContext, cgPoint.x, cgPoint.y - offset); - CGContextDrawPath (cgContext, kCGPathStroke); - } - cgDrawContext->releaseCGContext (cgContext); - } - CFRelease (line); - } + auto deviceContext = std::dynamic_pointer_cast (context); + if (!deviceContext) + return; + + CTLineRef line = createCTLine (context, macString, color); + if (!line) + return; + + CGPoint cgPoint = CGPointFromCPoint (point); + deviceContext->drawCTLine (line, cgPoint, fontRef, color, style & kUnderlineFace, + style & kStrikethroughFace, antialias); + CFRelease (line); } //----------------------------------------------------------------------------- -CCoord CoreTextFont::getStringWidth (CDrawContext* context, IPlatformString* string, bool antialias) const +CCoord CoreTextFont::getStringWidth (const PlatformGraphicsDeviceContextPtr& context, + IPlatformString* string, bool antialias) const { CCoord result = 0; MacString* macString = dynamic_cast (string); if (macString == nullptr) return result; - - CTLineRef line = createCTLine (context, macString); + + CTLineRef line = createCTLine (context, macString, kBlackCColor); if (line) { result = CTLineGetTypographicBounds (line, nullptr, nullptr, nullptr); diff --git a/vstgui/lib/platform/mac/cgdrawcontext.cpp b/vstgui/lib/platform/mac/cgdrawcontext.cpp deleted file mode 100644 index 1183a8de1..000000000 --- a/vstgui/lib/platform/mac/cgdrawcontext.cpp +++ /dev/null @@ -1,953 +0,0 @@ -// 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 "cgdrawcontext.h" - -#if MAC - -#include "macglobals.h" -#include "cgbitmap.h" -#include "quartzgraphicspath.h" -#include "cfontmac.h" -#include "../../cbitmap.h" -#include "../../cgradient.h" - -#ifndef CGFLOAT_DEFINED - #define CGFLOAT_DEFINED - using CGFloat = float; -#endif // CGFLOAT_DEFINED - -namespace VSTGUI { - -//----------------------------------------------------------------------------- -template -void DoGraphicStateSave (CGContextRef cgContext, Proc proc) -{ - CGContextSaveGState (cgContext); - proc (); - CGContextRestoreGState (cgContext); -} - -//----------------------------------------------------------------------------- -CGDrawContext::CGDrawContext (CGContextRef cgContext, const CRect& rect) -: COffscreenContext (rect), cgContext (cgContext), scaleFactor (1.) -{ - CFRetain (cgContext); - - // Get the scale for the context to check if it is for a Retina display - CGRect userRect = CGRectMake (0, 0, 100, 100); - CGRect deviceRect = CGContextConvertRectToDeviceSpace (cgContext, userRect); - scaleFactor = deviceRect.size.height / userRect.size.height; - - init (); -} - -//----------------------------------------------------------------------------- -CGDrawContext::CGDrawContext (CGBitmap* _bitmap) -: COffscreenContext (new CBitmap (_bitmap)) -, cgContext (_bitmap->createCGContext ()) -, scaleFactor (_bitmap->getScaleFactor ()) -{ - if (scaleFactor != 1.) - { - CGContextConcatCTM (cgContext, - CGAffineTransformMakeScale (static_cast (scaleFactor), - static_cast (scaleFactor))); - } - - init (); - bitmap->forget (); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::init () -{ - CGContextSaveGState (cgContext); - CGContextSetAllowsAntialiasing (cgContext, true); - CGContextSetAllowsFontSmoothing (cgContext, true); - CGContextSetAllowsFontSubpixelPositioning (cgContext, true); - CGContextSetAllowsFontSubpixelQuantization (cgContext, true); - CGContextSetShouldAntialias (cgContext, false); - CGContextSetFillColorSpace (cgContext, GetCGColorSpace ()); - CGContextSetStrokeColorSpace (cgContext, GetCGColorSpace ()); - CGContextSaveGState (cgContext); - CGAffineTransform cgCTM = CGAffineTransformMake (1.0, 0.0, 0.0, -1.0, 0.0, 0.0); - CGContextSetTextMatrix (cgContext, cgCTM); - - CDrawContext::init (); -} - -//----------------------------------------------------------------------------- -CGDrawContext::~CGDrawContext () noexcept -{ - CGContextRestoreGState (cgContext); // restore the original state - CGContextRestoreGState (cgContext); // we need to do it twice !!! - CFRelease (cgContext); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::endDraw () -{ - if (bitmap && bitmap->getPlatformBitmap ()) - { - if (auto cgBitmap = bitmap->getPlatformBitmap ().cast ()) - cgBitmap->setDirty (); - } - bitmapDrawCount.clear (); -} - -//----------------------------------------------------------------------------- -CGraphicsPath* CGDrawContext::createGraphicsPath () -{ - return new CGraphicsPath (CGGraphicsPathFactory::instance ()); -} - -//----------------------------------------------------------------------------- -CGraphicsPath* CGDrawContext::createTextPath (const CFontRef font, UTF8StringPtr text) -{ - if (auto path = - CGGraphicsPathFactory::instance ()->createTextPath (font->getPlatformFont (), text)) - { - return new CGraphicsPath (CGGraphicsPathFactory::instance (), std::move (path)); - } - return nullptr; -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawGraphicsPath (CGraphicsPath* path, PathDrawMode mode, CGraphicsTransform* t) -{ - if (!path) - return; - const auto& graphicsPath = path->getPlatformPath (PlatformGraphicsPathFillMode::Ignored); - if (!graphicsPath) - return; - auto cgPath = dynamic_cast (graphicsPath.get ()); - if (!cgPath) - return; - - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - CGPathDrawingMode cgMode; - switch (mode) - { - case kPathFilledEvenOdd: - { - cgMode = kCGPathEOFill; - break; - } - case kPathStroked: - { - cgMode = kCGPathStroke; - applyLineStyle (context); - break; - } - default: - { - cgMode = kCGPathFill; - break; - } - } - - DoGraphicStateSave (context, [&] () { - if (t) - { - CGAffineTransform transform = createCGAffineTransform (*t); - CGContextConcatCTM (context, transform); - } - if (getDrawMode ().integralMode () && getDrawMode ().aliasing ()) - { - DoGraphicStateSave (context, [&] () { - applyLineWidthCTM (context); - cgPath->pixelAlign ( - [] (const CGPoint& p, void* context) { - auto cgDrawContext = reinterpret_cast (context); - return cgDrawContext->pixelAlligned (p); - }, - this); - }); - CGContextAddPath (context, cgPath->getCGPathRef ()); - } - else - CGContextAddPath (context, cgPath->getCGPathRef ()); - - }); - CGContextDrawPath (context, cgMode); - - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, - const CPoint& startPoint, const CPoint& endPoint, - bool evenOdd, CGraphicsTransform* t) -{ - if (path == nullptr) - return; - - auto cgGradient = dynamic_cast (gradient.getPlatformGradient ().get ()); - if (cgGradient == nullptr) - return; - - const auto& graphicsPath = path->getPlatformPath (PlatformGraphicsPathFillMode::Ignored); - if (!graphicsPath) - return; - auto cgPath = dynamic_cast (graphicsPath.get ()); - if (!cgPath) - return; - - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - CGPoint start = CGPointFromCPoint (startPoint); - CGPoint end = CGPointFromCPoint (endPoint); - DoGraphicStateSave (context, [&] () { - if (getDrawMode ().integralMode ()) - { - start = pixelAlligned (start); - end = pixelAlligned (end); - } - if (t) - { - CGAffineTransform transform = createCGAffineTransform (*t); - CGContextConcatCTM (context, transform); - } - if (getDrawMode ().integralMode () && getDrawMode ().aliasing ()) - { - cgPath->pixelAlign ( - [] (const CGPoint& p, void* context) { - auto cgDrawContext = reinterpret_cast (context); - return cgDrawContext->pixelAlligned (p); - }, - this); - } - - CGContextAddPath (context, cgPath->getCGPathRef ()); - }); - - if (evenOdd) - CGContextEOClip (context); - else - CGContextClip (context); - - CGContextDrawLinearGradient (context, *cgGradient, start, end, - kCGGradientDrawsBeforeStartLocation | - kCGGradientDrawsAfterEndLocation); - - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, - const CPoint& center, CCoord radius, - const CPoint& originOffset, bool evenOdd, - CGraphicsTransform* t) -{ - if (path == nullptr) - return; - - auto cgGradient = dynamic_cast (gradient.getPlatformGradient ().get ()); - if (cgGradient == nullptr) - return; - - const auto& graphicsPath = path->getPlatformPath (PlatformGraphicsPathFillMode::Ignored); - if (!graphicsPath) - return; - auto cgPath = dynamic_cast (graphicsPath.get ()); - if (!cgPath) - return; - - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - DoGraphicStateSave (context, [&] () { - if (t) - { - CGAffineTransform transform = createCGAffineTransform (*t); - CGContextConcatCTM (context, transform); - } - if (getDrawMode ().integralMode () && getDrawMode ().aliasing ()) - { - cgPath->pixelAlign ( - [] (const CGPoint& p, void* context) { - auto cgDrawContext = reinterpret_cast (context); - return cgDrawContext->pixelAlligned (p); - }, - this); - } - - CGContextAddPath (context, cgPath->getCGPathRef ()); - }); - - if (evenOdd) - CGContextEOClip (context); - else - CGContextClip (context); - - CPoint startCenter = center + originOffset; - CGContextDrawRadialGradient (context, *cgGradient, CGPointFromCPoint (startCenter), 0, - CGPointFromCPoint (center), static_cast (radius), - kCGGradientDrawsBeforeStartLocation | - kCGGradientDrawsAfterEndLocation); - - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::saveGlobalState () -{ - CGContextSaveGState (cgContext); - CDrawContext::saveGlobalState (); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::restoreGlobalState () -{ - CDrawContext::restoreGlobalState (); - CGContextRestoreGState (cgContext); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setGlobalAlpha (float newAlpha) -{ - if (newAlpha == getCurrentState ().globalAlpha) - return; - - CGContextSetAlpha (cgContext, newAlpha); - - CDrawContext::setGlobalAlpha (newAlpha); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setLineStyle (const CLineStyle& style) -{ - if (getCurrentState ().lineStyle == style) - return; - - CDrawContext::setLineStyle (style); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setLineWidth (CCoord width) -{ - if (getCurrentState ().frameWidth == width) - return; - - CGContextSetLineWidth (cgContext, static_cast (width)); - - CDrawContext::setLineWidth (width); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setDrawMode (CDrawMode mode) -{ - if (cgContext) - CGContextSetShouldAntialias (cgContext, mode.antiAliasing ()); - - CDrawContext::setDrawMode (mode); -} - -//------------------------------------------------------------------------------ -void CGDrawContext::setClipRect (const CRect& clip) -{ - CDrawContext::setClipRect (clip); -} - -//------------------------------------------------------------------------------ -void CGDrawContext::resetClipRect () -{ - CDrawContext::resetClipRect (); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawLine (const LinePair& line) -{ - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - applyLineStyle (context); - - CGContextBeginPath (context); - CGPoint first = CGPointFromCPoint (line.first); - CGPoint second = CGPointFromCPoint (line.second); - - if (getDrawMode ().integralMode ()) - { - first = pixelAlligned (first); - second = pixelAlligned (second); - applyLineWidthCTM (context); - } - - CGContextMoveToPoint (context, first.x, first.y); - CGContextAddLineToPoint (context, second.x, second.y); - - CGContextDrawPath (context, kCGPathStroke); - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawLines (const LineList& lines) -{ - if (lines.size () == 0) - return; - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - applyLineStyle (context); - - static constexpr auto numStackPoints = 32; - CGPoint stackPoints[numStackPoints]; - - auto cgPointsPtr = (lines.size () * 2) < numStackPoints - ? nullptr - : std::unique_ptr (new CGPoint[lines.size () * 2]); - auto cgPoints = cgPointsPtr ? cgPointsPtr.get () : stackPoints; - uint32_t index = 0; - if (getDrawMode ().integralMode ()) - { - for (const auto& line : lines) - { - cgPoints[index] = pixelAlligned (CGPointFromCPoint (line.first)); - cgPoints[index + 1] = pixelAlligned (CGPointFromCPoint (line.second)); - index += 2; - } - } - else - { - for (const auto& line : lines) - { - cgPoints[index] = CGPointFromCPoint (line.first); - cgPoints[index + 1] = CGPointFromCPoint (line.second); - index += 2; - } - } - - if (getDrawMode ().integralMode ()) - applyLineWidthCTM (context); - - const size_t maxPointsPerIteration = 16; - const CGPoint* pointPtr = cgPoints; - size_t numPoints = lines.size () * 2; - while (numPoints) - { - size_t np = std::min (numPoints, std::min (maxPointsPerIteration, numPoints)); - CGContextStrokeLineSegments (context, pointPtr, np); - numPoints -= np; - pointPtr += np; - } - - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle) -{ - if (polygonPointList.size () == 0) - return; - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - CGPathDrawingMode m; - switch (drawStyle) - { - case kDrawFilled: m = kCGPathFill; break; - case kDrawFilledAndStroked: m = kCGPathFillStroke; break; - default: m = kCGPathStroke; break; - } - applyLineStyle (context); - - CGContextBeginPath (context); - CGPoint p = CGPointFromCPoint (polygonPointList[0]); - if (getDrawMode ().integralMode ()) - p = pixelAlligned (p); - CGContextMoveToPoint (context, p.x, p.y); - for (uint32_t i = 1; i < polygonPointList.size (); i++) - { - p = CGPointFromCPoint (polygonPointList[i]); - if (getDrawMode ().integralMode ()) - p = pixelAlligned (p); - CGContextAddLineToPoint (context, p.x, p.y); - } - CGContextDrawPath (context, m); - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::applyLineWidthCTM (CGContextRef context) const -{ - int32_t frameWidth = static_cast (getCurrentState ().frameWidth); - if (static_cast (frameWidth) == getCurrentState ().frameWidth && frameWidth % 2) - CGContextTranslateCTM (context, 0.5, 0.5); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawRect (const CRect& rect, const CDrawStyle drawStyle) -{ - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - CGRect r = CGRectFromCRect (rect); - if (drawStyle != kDrawFilled) - { - r.size.width -= 1.; - r.size.height -= 1.; - } - - CGPathDrawingMode m; - switch (drawStyle) - { - case kDrawFilled: m = kCGPathFill; break; - case kDrawFilledAndStroked: m = kCGPathFillStroke; break; - default: m = kCGPathStroke; break; - } - applyLineStyle (context); - - if (getDrawMode ().integralMode ()) - { - r = pixelAlligned (r); - if (drawStyle != kDrawFilled) - applyLineWidthCTM (context); - } - - CGContextBeginPath (context); - CGContextAddRect (context, r); - CGContextDrawPath (context, m); - - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawEllipse (const CRect& rect, const CDrawStyle drawStyle) -{ - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - CGRect r = CGRectFromCRect (rect); - if (drawStyle != kDrawFilled) - { - r.size.width -= 1.; - r.size.height -= 1.; - } - - CGPathDrawingMode m; - switch (drawStyle) - { - case kDrawFilled: m = kCGPathFill; break; - case kDrawFilledAndStroked: m = kCGPathFillStroke; break; - default: m = kCGPathStroke; break; - } - applyLineStyle (context); - if (getDrawMode ().integralMode ()) - { - if (drawStyle != kDrawFilled) - applyLineWidthCTM (context); - r = pixelAlligned (r); - } - - CGContextAddEllipseInRect (context, r); - CGContextDrawPath (context, m); - - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawPoint (const CPoint& point, const CColor& color) -{ - saveGlobalState (); - - setLineWidth (1); - setFrameColor (color); - CPoint point2 (point); - point2.x++; - COffscreenContext::drawLine (point, point2); - - restoreGlobalState (); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::addOvalToPath (CGContextRef c, CPoint center, CGFloat a, CGFloat b, - CGFloat start_angle, CGFloat end_angle) const -{ - DoGraphicStateSave (c, [&] () { - CGContextTranslateCTM (c, static_cast (center.x), static_cast (center.y)); - CGContextScaleCTM (c, a, b); - - auto startAngle = radians (start_angle); - auto endAngle = radians (end_angle); - if (a != b) - { - startAngle = std::atan2 (std::sin (startAngle) * a, std::cos (startAngle) * b); - endAngle = std::atan2 (std::sin (endAngle) * a, std::cos (endAngle) * b); - } - CGContextMoveToPoint (c, static_cast (std::cos (startAngle)), - static_cast (std::sin (startAngle))); - CGContextAddArc (c, 0, 0, 1, static_cast (startAngle), - static_cast (endAngle), 0); - }); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawArc (const CRect& rect, const float _startAngle, const float _endAngle, - const CDrawStyle drawStyle) -{ - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - CGPathDrawingMode m; - switch (drawStyle) - { - case kDrawFilled: m = kCGPathFill; break; - case kDrawFilledAndStroked: m = kCGPathFillStroke; break; - default: m = kCGPathStroke; break; - } - applyLineStyle (context); - - CGContextBeginPath (context); - addOvalToPath ( - context, CPoint (rect.left + rect.getWidth () / 2., rect.top + rect.getHeight () / 2.), - static_cast (rect.getWidth () / 2.), - static_cast (rect.getHeight () / 2.), _startAngle, _endAngle); - CGContextDrawPath (context, m); - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawBitmapNinePartTiled (CBitmap* bitmap, const CRect& inRect, - const CNinePartTiledDescription& desc, float alpha) -{ - // TODO: When drawing on a scaled transform the bitmaps are not alligned correctly - CDrawContext::drawBitmapNinePartTiled (bitmap, inRect, desc, alpha); - return; -} - -//----------------------------------------------------------------------------- -void CGDrawContext::fillRectWithBitmap (CBitmap* bitmap, const CRect& srcRect, const CRect& dstRect, - float alpha) -{ - if (bitmap == nullptr || alpha == 0.f || srcRect.isEmpty () || dstRect.isEmpty ()) - return; - - if (!(srcRect.left == 0 && srcRect.right == 0 && srcRect.right == bitmap->getWidth () && - srcRect.bottom == bitmap->getHeight ())) - { - // CGContextDrawTiledImage does not work with parts of a bitmap - CDrawContext::fillRectWithBitmap (bitmap, srcRect, dstRect, alpha); - return; - } - - auto platformBitmap = bitmap->getBestPlatformBitmapForScaleFactor (scaleFactor); - if (!platformBitmap) - return; - CPoint bitmapSize = platformBitmap->getSize (); - if (srcRect.right > bitmapSize.x || srcRect.bottom > bitmapSize.y) - return; - - auto cgBitmap = platformBitmap.cast (); - if (CGImageRef image = cgBitmap ? cgBitmap->getCGImage () : nullptr) - { - if (auto context = beginCGContext (false, true)) - { - // TODO: Check if this works with retina images - CGRect clipRect = CGRectFromCRect (dstRect); - clipRect.origin.y = -(clipRect.origin.y) - clipRect.size.height; - clipRect = pixelAlligned (clipRect); - CGContextClipToRect (context, clipRect); - - CGRect r = {}; - r.size.width = CGImageGetWidth (image); - r.size.height = CGImageGetHeight (image); - - setCGDrawContextQuality (context); - - CGContextDrawTiledImage (context, r, image); - - releaseCGContext (context); - } - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawBitmap (CBitmap* bitmap, const CRect& inRect, const CPoint& inOffset, - float alpha) -{ - if (bitmap == nullptr || alpha == 0.f) - return; - double transformedScaleFactor = scaleFactor; - CGraphicsTransform t = getCurrentTransform (); - if (t.m11 == t.m22 && t.m12 == 0 && t.m21 == 0) - transformedScaleFactor *= t.m11; - auto platformBitmap = bitmap->getBestPlatformBitmapForScaleFactor (transformedScaleFactor); - if (!platformBitmap) - return; - auto cgBitmap = platformBitmap.cast (); - if (CGImageRef image = cgBitmap ? cgBitmap->getCGImage () : nullptr) - { - if (auto context = beginCGContext (false, true)) - { - CGLayerRef layer = nullptr; - if (scaleFactor == 1.) - { - layer = cgBitmap->getCGLayer (); - if (layer == nullptr) - { - auto it = bitmapDrawCount.find (cgBitmap); - if (it == bitmapDrawCount.end ()) - { - bitmapDrawCount.emplace (cgBitmap, 1); - } - else - { - it->second++; - layer = cgBitmap->createCGLayer (context); - } - } - } - - drawCGImageRef (context, image, layer, cgBitmap->getScaleFactor (), inRect, inOffset, - alpha, bitmap); - - releaseCGContext (context); - } - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::drawCGImageRef (CGContextRef context, CGImageRef image, CGLayerRef layer, - double bitmapScaleFactor, const CRect& inRect, - const CPoint& inOffset, float alpha, CBitmap* bitmap) -{ - setCGDrawContextQuality (context); - - CRect rect (inRect); - CPoint offset (inOffset); - - CGContextSetAlpha (context, (CGFloat)alpha * getCurrentState ().globalAlpha); - - CGRect dest; - dest.origin.x = static_cast (rect.left - offset.x); - dest.origin.y = static_cast (-(rect.top) - (bitmap->getHeight () - offset.y)); - dest.size.width = static_cast (bitmap->getWidth ()); - dest.size.height = static_cast (bitmap->getHeight ()); - - CGRect clipRect; - clipRect.origin.x = static_cast (rect.left); - clipRect.origin.y = static_cast (-(rect.top) - rect.getHeight ()); - clipRect.size.width = static_cast (rect.getWidth ()); - clipRect.size.height = static_cast (rect.getHeight ()); - - if (bitmapScaleFactor != 1.) - { - CGContextConcatCTM ( - context, CGAffineTransformMakeScale (static_cast (1. / bitmapScaleFactor), - static_cast (1. / bitmapScaleFactor))); - CGAffineTransform transform = CGAffineTransformMakeScale ( - static_cast (bitmapScaleFactor), static_cast (bitmapScaleFactor)); - clipRect.origin = CGPointApplyAffineTransform (clipRect.origin, transform); - clipRect.size = CGSizeApplyAffineTransform (clipRect.size, transform); - dest.origin = CGPointApplyAffineTransform (dest.origin, transform); - dest.size = CGSizeApplyAffineTransform (dest.size, transform); - } - // dest.origin = pixelAlligned (dest.origin); - clipRect.origin = pixelAlligned (clipRect.origin); - - CGContextClipToRect (context, clipRect); - - if (layer) - { - CGContextDrawLayerInRect (context, dest, layer); - } - else - { - CGContextDrawImage (context, dest, image); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setCGDrawContextQuality (CGContextRef context) -{ - switch (getCurrentState ().bitmapQuality) - { - case BitmapInterpolationQuality::kLow: - { - CGContextSetShouldAntialias (context, false); - CGContextSetInterpolationQuality (context, kCGInterpolationNone); - break; - } - case BitmapInterpolationQuality::kMedium: - { - CGContextSetShouldAntialias (context, true); - CGContextSetInterpolationQuality (context, kCGInterpolationMedium); - break; - } - case BitmapInterpolationQuality::kHigh: - { - CGContextSetShouldAntialias (context, true); - CGContextSetInterpolationQuality (context, kCGInterpolationHigh); - break; - } - default: break; - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::clearRect (const CRect& rect) -{ - if (auto context = beginCGContext (true, getDrawMode ().integralMode ())) - { - CGRect cgRect = CGRectFromCRect (rect); - if (getDrawMode ().integralMode ()) - { - cgRect = pixelAlligned (cgRect); - } - CGContextClearRect (context, cgRect); - releaseCGContext (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setFontColor (const CColor& color) -{ - if (getCurrentState ().fontColor == color) - return; - - CDrawContext::setFontColor (color); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setFrameColor (const CColor& color) -{ - if (getCurrentState ().frameColor == color) - return; - - if (cgContext) - CGContextSetStrokeColorWithColor (cgContext, getCGColor (color)); - - CDrawContext::setFrameColor (color); -} - -//----------------------------------------------------------------------------- -void CGDrawContext::setFillColor (const CColor& color) -{ - if (getCurrentState ().fillColor == color) - return; - - if (cgContext) - CGContextSetFillColorWithColor (cgContext, getCGColor (color)); - - CDrawContext::setFillColor (color); -} - -#if DEBUG -bool showClip = false; -#endif - -//----------------------------------------------------------------------------- -CGContextRef CGDrawContext::beginCGContext (bool swapYAxis, bool integralOffset) -{ - if (cgContext) - { - if (getCurrentState ().clipRect.isEmpty ()) - return nullptr; - - CGContextSaveGState (cgContext); - - CGRect cgClipRect = CGRectFromCRect (getCurrentState ().clipRect); - if (integralOffset) - cgClipRect = pixelAlligned (cgClipRect); - CGContextClipToRect (cgContext, cgClipRect); -#if DEBUG - if (showClip) - { - CGContextSetRGBFillColor (cgContext, 1, 0, 0, 0.5); - CGContextFillRect (cgContext, cgClipRect); - } -#endif - - if (getCurrentTransform ().isInvariant () == false) - { - CGraphicsTransform t = getCurrentTransform (); - if (integralOffset) - { - CGPoint p = pixelAlligned (CGPointFromCPoint (CPoint (t.dx, t.dy))); - t.dx = p.x; - t.dy = p.y; - } - CGContextConcatCTM (cgContext, createCGAffineTransform (t)); - } - - if (!swapYAxis) - CGContextScaleCTM (cgContext, 1, -1); - - return cgContext; - } - return nullptr; -} - -//----------------------------------------------------------------------------- -void CGDrawContext::releaseCGContext (CGContextRef context) -{ - if (context) - { - CGContextRestoreGState (context); - } -} - -//----------------------------------------------------------------------------- -void CGDrawContext::applyLineStyle (CGContextRef context) -{ - switch (getCurrentState ().lineStyle.getLineCap ()) - { - case CLineStyle::kLineCapButt: CGContextSetLineCap (context, kCGLineCapButt); break; - case CLineStyle::kLineCapRound: CGContextSetLineCap (context, kCGLineCapRound); break; - case CLineStyle::kLineCapSquare: CGContextSetLineCap (context, kCGLineCapSquare); break; - } - switch (getCurrentState ().lineStyle.getLineJoin ()) - { - case CLineStyle::kLineJoinMiter: CGContextSetLineJoin (context, kCGLineJoinMiter); break; - case CLineStyle::kLineJoinRound: CGContextSetLineJoin (context, kCGLineJoinRound); break; - case CLineStyle::kLineJoinBevel: CGContextSetLineJoin (context, kCGLineJoinBevel); break; - } - if (getCurrentState ().lineStyle.getDashCount () > 0) - { - CGFloat* dashLengths = new CGFloat[getCurrentState ().lineStyle.getDashCount ()]; - for (uint32_t i = 0; i < getCurrentState ().lineStyle.getDashCount (); i++) - { - dashLengths[i] = static_cast ( - getCurrentState ().frameWidth * getCurrentState ().lineStyle.getDashLengths ()[i]); - } - CGContextSetLineDash (context, - static_cast (getCurrentState ().lineStyle.getDashPhase ()), - dashLengths, getCurrentState ().lineStyle.getDashCount ()); - delete[] dashLengths; - } -} - -//----------------------------------------------------------------------------- -CGRect CGDrawContext::pixelAlligned (const CGRect& r) const -{ - CGRect result; - result.origin = CGContextConvertPointToDeviceSpace (cgContext, r.origin); - result.size = CGContextConvertSizeToDeviceSpace (cgContext, r.size); - result.origin.x = static_cast (std::round (result.origin.x)); - result.origin.y = static_cast (std::round (result.origin.y)); - result.size.width = static_cast (std::round (result.size.width)); - result.size.height = static_cast (std::round (result.size.height)); - result.origin = CGContextConvertPointToUserSpace (cgContext, result.origin); - result.size = CGContextConvertSizeToUserSpace (cgContext, result.size); - return result; -} - -//----------------------------------------------------------------------------- -CGPoint CGDrawContext::pixelAlligned (const CGPoint& p) const -{ - CGPoint result = CGContextConvertPointToDeviceSpace (cgContext, p); - result.x = static_cast (std::round (result.x)); - result.y = static_cast (std::round (result.y)); - result = CGContextConvertPointToUserSpace (cgContext, result); - return result; -} - -} // VSTGUI - -#endif // MAC diff --git a/vstgui/lib/platform/mac/cgdrawcontext.h b/vstgui/lib/platform/mac/cgdrawcontext.h deleted file mode 100644 index 0296db671..000000000 --- a/vstgui/lib/platform/mac/cgdrawcontext.h +++ /dev/null @@ -1,89 +0,0 @@ -// 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 "../../coffscreencontext.h" - -#if MAC - -#if TARGET_OS_IPHONE - #include - #include -#else - #include -#endif - -#include - -namespace VSTGUI { -class CGBitmap; - -//----------------------------------------------------------------------------- -class CGDrawContext : public COffscreenContext -{ -public: - CGDrawContext (CGContextRef cgContext, const CRect& rect); - explicit CGDrawContext (CGBitmap* bitmap); - ~CGDrawContext () noexcept override; - - void drawLine (const LinePair& line) override; - void drawLines (const LineList& lines) override; - void drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle = kDrawStroked) override; - void drawRect (const CRect &rect, const CDrawStyle drawStyle = kDrawStroked) override; - void drawArc (const CRect &rect, const float startAngle1, const float endAngle2, const CDrawStyle drawStyle = kDrawStroked) override; - void drawEllipse (const CRect &rect, const CDrawStyle drawStyle = kDrawStroked) override; - void drawPoint (const CPoint &point, const CColor& color) override; - void drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset = CPoint (0, 0), float alpha = 1.f) override; - void drawBitmapNinePartTiled (CBitmap* bitmap, const CRect& dest, const CNinePartTiledDescription& desc, float alpha = 1.f) override; - void fillRectWithBitmap (CBitmap* bitmap, const CRect& srcRect, const CRect& dstRect, float alpha) override; - void clearRect (const CRect& rect) override; - void setLineStyle (const CLineStyle& style) override; - void setLineWidth (CCoord width) override; - void setDrawMode (CDrawMode mode) override; - void setClipRect (const CRect &clip) override; - void resetClipRect () override; - void setFillColor (const CColor& color) override; - void setFrameColor (const CColor& color) override; - void setFontColor (const CColor& color) override; - void setGlobalAlpha (float newAlpha) override; - void saveGlobalState () override; - void restoreGlobalState () override; - void endDraw () override; - CGraphicsPath* createGraphicsPath () override; - CGraphicsPath* createTextPath (const CFontRef font, UTF8StringPtr text) override; - void drawGraphicsPath (CGraphicsPath* path, PathDrawMode mode = kPathFilled, CGraphicsTransform* transformation = nullptr) override; - void fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& startPoint, const CPoint& endPoint, bool evenOdd = false, CGraphicsTransform* transformation = nullptr) override; - void fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& center, CCoord radius, const CPoint& originOffset = CPoint (0, 0), bool evenOdd = false, CGraphicsTransform* transformation = nullptr) override; - double getScaleFactor () const override { return scaleFactor; } - - CGContextRef beginCGContext (bool swapYAxis = false, bool integralOffset = false); - void releaseCGContext (CGContextRef context); - - CGContextRef getCGContext () const { return cgContext; } - void applyLineStyle (CGContextRef context); - void applyLineWidthCTM (CGContextRef context) const; - - CGRect pixelAlligned (const CGRect& r) const; - CGPoint pixelAlligned (const CGPoint& p) const; - -//------------------------------------------------------------------------------------ -protected: - void init () override; - void drawCGImageRef (CGContextRef context, CGImageRef image, CGLayerRef layer, double imageScaleFactor, const CRect& inRect, const CPoint& inOffset, float alpha, CBitmap* bitmap); - void setCGDrawContextQuality (CGContextRef context); - void addOvalToPath (CGContextRef c, CPoint center, CGFloat a, CGFloat b, CGFloat start_angle, - CGFloat end_angle) const; - - CGContextRef cgContext; - - using BitmapDrawCountMap = std::map; - BitmapDrawCountMap bitmapDrawCount; - - double scaleFactor; -}; - -} // VSTGUI - -#endif // MAC diff --git a/vstgui/lib/platform/mac/cocoa/cocoatextedit.mm b/vstgui/lib/platform/mac/cocoa/cocoatextedit.mm index 02c4ad1de..4e906958b 100644 --- a/vstgui/lib/platform/mac/cocoa/cocoatextedit.mm +++ b/vstgui/lib/platform/mac/cocoa/cocoatextedit.mm @@ -188,9 +188,6 @@ static id Init (id self, SEL _cmd, void* textEdit) if (auto tv = static_cast ([[self window] fieldEditor:YES forObject:self])) tv.insertionPointColor = nsColorFromCColor (tec->platformGetFontColor ()); - if ([frameView respondsToSelector:@selector (makeSubViewFirstResponder:)]) - [frameView performSelector:@selector (makeSubViewFirstResponder:) withObject:self]; - dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t) (1 * NSEC_PER_MSEC)), dispatch_get_main_queue (), ^{ [[self window] makeFirstResponder:self]; diff --git a/vstgui/lib/platform/mac/cocoa/nsviewframe.h b/vstgui/lib/platform/mac/cocoa/nsviewframe.h index f470a68a6..fec78e272 100644 --- a/vstgui/lib/platform/mac/cocoa/nsviewframe.h +++ b/vstgui/lib/platform/mac/cocoa/nsviewframe.h @@ -9,9 +9,9 @@ #if MAC_COCOA && !TARGET_OS_IPHONE #include "../../platform_macos.h" -#include "../../../cview.h" #include "../../../cinvalidrectlist.h" #include "../../../idatapackage.h" +#import "../coregraphicsdevicecontext.h" #include "nsviewdraggingsession.h" #include @@ -47,8 +47,6 @@ class NSViewFrame : public IPlatformFrame, public ICocoaPlatformFrame, public IP #if VSTGUI_ENABLE_DEPRECATED_METHODS void setLastDragOperationResult (DragResult result) { lastDragOperationResult = result; } #endif - void setIgnoreNextResignFirstResponder (bool state) { ignoreNextResignFirstResponder = state; } - bool getIgnoreNextResignFirstResponder () const { return ignoreNextResignFirstResponder; } void setDragDataPackage (SharedPointer&& package) { dragDataPackage = std::move (package); } const SharedPointer& getDragDataPackage () const { return dragDataPackage; } @@ -56,8 +54,8 @@ class NSViewFrame : public IPlatformFrame, public ICocoaPlatformFrame, public IP void initTrackingArea (); void scaleFactorChanged (double newScaleFactor); void cursorUpdate (); - virtual void drawRect (NSRect* rect); void drawLayer (CALayer* layer, CGContextRef ctx); + void drawRect (NSRect* rect); bool onMouseDown (NSEvent* evt); bool onMouseUp (NSEvent* evt); bool onMouseMoved (NSEvent* evt); @@ -97,6 +95,7 @@ class NSViewFrame : public IPlatformFrame, public ICocoaPlatformFrame, public IP //----------------------------------------------------------------------------- protected: + void draw (CGContextRef context, CRect updateRect, double scaleFactor); void addDebugRedrawRect (CRect r, bool isClipBoundingBox = false); NSView* nsView {nullptr}; @@ -110,7 +109,6 @@ class NSViewFrame : public IPlatformFrame, public ICocoaPlatformFrame, public IP #if VSTGUI_ENABLE_DEPRECATED_METHODS DragResult lastDragOperationResult; #endif - bool ignoreNextResignFirstResponder; bool trackingAreaInitialized; bool inDraw; bool useInvalidRects {false}; diff --git a/vstgui/lib/platform/mac/cocoa/nsviewframe.mm b/vstgui/lib/platform/mac/cocoa/nsviewframe.mm index bca36f481..f79821af4 100644 --- a/vstgui/lib/platform/mac/cocoa/nsviewframe.mm +++ b/vstgui/lib/platform/mac/cocoa/nsviewframe.mm @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -14,7 +14,6 @@ #import "autoreleasepool.h" #import "../macclipboard.h" #import "../macfactory.h" -#import "../cgdrawcontext.h" #import "../cgbitmap.h" #import "../quartzgraphicspath.h" #import "../caviewlayer.h" @@ -187,6 +186,8 @@ static Class CreateClass () .addMethod (@selector (acceptsFirstResponder), acceptsFirstResponder) .addMethod (@selector (becomeFirstResponder), becomeFirstResponder) .addMethod (@selector (resignFirstResponder), resignFirstResponder) + .addMethod (@selector (nextValidKeyView), nextValidKeyView) + .addMethod (@selector (previousValidKeyView), previousValidKeyView) .addMethod (@selector (canBecomeKeyView), canBecomeKeyView) .addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping) .addMethod (@selector (isOpaque), isOpaque) @@ -217,7 +218,6 @@ static Class CreateClass () .addMethod (@selector (keyUp:), keyUp) .addMethod (@selector (magnifyWithEvent:), magnifyWithEvent) .addMethod (@selector (focusRingType), focusRingType) - .addMethod (@selector (makeSubViewFirstResponder:), makeSubViewFirstResponder) .addMethod (@selector (draggingEntered:), draggingEntered) .addMethod (@selector (draggingUpdated:), draggingUpdated) .addMethod (@selector (draggingExited:), draggingExited) @@ -309,18 +309,6 @@ static id init (id self, SEL _cmd, void* _frame, NSView* parentView, const void* static NSFocusRingType focusRingType (id self) { return NSFocusRingTypeNone; } static BOOL shouldBeTreatedAsInkEvent (id self, SEL _cmd, NSEvent* event) { return NO; } - //------------------------------------------------------------------------------------ - static void makeSubViewFirstResponder (id self, SEL _cmd, NSResponder* newFirstResponder) - { - NSViewFrame* nsFrame = getNSViewFrame (self); - if (nsFrame) - { - nsFrame->setIgnoreNextResignFirstResponder (true); - [[self window] makeFirstResponder:newFirstResponder]; - nsFrame->setIgnoreNextResignFirstResponder (false); - } - } - //------------------------------------------------------------------------------------ static BOOL becomeFirstResponder (id self, SEL _cmd) { @@ -341,15 +329,9 @@ static BOOL resignFirstResponder (id self, SEL _cmd) firstResponder = nil; if (firstResponder) { - NSViewFrame* nsFrame = getNSViewFrame (self); - if (nsFrame && nsFrame->getIgnoreNextResignFirstResponder ()) + if ([firstResponder isDescendantOf:self]) { - while (firstResponder != self && firstResponder != nil) - firstResponder = [firstResponder superview]; - if (firstResponder == self && [[self window] isKeyWindow]) - { - return YES; - } + return YES; } IPlatformFrameCallback* frame = getFrame (self); if (frame) @@ -358,6 +340,26 @@ static BOOL resignFirstResponder (id self, SEL _cmd) return YES; } + //------------------------------------------------------------------------------------ + static NSView* nextValidKeyView (id self, SEL _cmd) + { + auto view = + makeInstance (self).callSuper (@selector (nextValidKeyView)); + while (view != self && [view isDescendantOf:self]) + view = view.nextValidKeyView; + return view; + } + + //------------------------------------------------------------------------------------ + static NSView* previousValidKeyView (id self, SEL _cmd) + { + auto view = makeInstance (self).callSuper ( + @selector (previousValidKeyView)); + while (view != self && [view isDescendantOf:self]) + view = view.previousValidKeyView; + return view; + } + //------------------------------------------------------------------------------------ static void updateTrackingAreas (id self, SEL _cmd) { @@ -937,7 +939,6 @@ static void draggedImageEndedAtOperation (id self, SEL _cmd, NSImage* image, NSP NSViewFrame::NSViewFrame (IPlatformFrameCallback* frame, const CRect& size, NSView* parent, IPlatformFrameConfig* config) : IPlatformFrame (frame) -, ignoreNextResignFirstResponder (false) , trackingAreaInitialized (false) , inDraw (false) , cursor (kCursorDefault) @@ -949,33 +950,29 @@ static void draggedImageEndedAtOperation (id self, SEL _cmd, NSImage* image, NSP if (cocoaConfig && cocoaConfig->flags & CocoaFrameConfig::kNoCALayer) return; - auto processInfo = [NSProcessInfo processInfo]; - if ([processInfo respondsToSelector:@selector(operatingSystemVersion)]) + // on Mac OS X 10.11 we activate layer drawing as this fixes a few issues like that only a + // few parts of a window are updated permanently when scrolling or manipulating a control + // while other parts are only updated when the malipulation ended, or CNinePartTiledBitmap + // are drawn incorrectly when scaled. + if (@available (macOS 10.11, *)) { - // on Mac OS X 10.11 we activate layer drawing as this fixes a few issues like that only a - // few parts of a window are updated permanently when scrolling or manipulating a control - // while other parts are only updated when the malipulation ended, or CNinePartTiledBitmap - // are drawn incorrectly when scaled. - if (@available (macOS 10.11, *)) + [nsView setWantsLayer:YES]; + caLayer = [CALayer new]; + caLayer.delegate = static_cast> (nsView); + caLayer.frame = nsView.layer.bounds; + [caLayer setContentsScale:nsView.layer.contentsScale]; + [nsView.layer addSublayer:caLayer]; + useInvalidRects = true; + if (@available (macOS 10.13, *)) { - [nsView setWantsLayer:YES]; - caLayer = [CALayer new]; - caLayer.delegate = static_cast> (nsView); - caLayer.frame = nsView.layer.bounds; - [caLayer setContentsScale:nsView.layer.contentsScale]; - [nsView.layer addSublayer:caLayer]; - useInvalidRects = true; - if (@available (macOS 10.13, *)) + nsView.layer.contentsFormat = kCAContentsFormatRGBA8Uint; + // asynchronous layer drawing or drawing only dirty rectangles are exclusive as + // the CoreGraphics engineers decided to be clever and join dirty rectangles without + // letting us know + if (getPlatformFactory ().asMacFactory ()->getUseAsynchronousLayerDrawing ()) { - nsView.layer.contentsFormat = kCAContentsFormatRGBA8Uint; - // asynchronous layer drawing or drawing only dirty rectangles are exclusive as - // the CoreGraphics engineers decided to be clever and join dirty rectangles without - // letting us know - if (getPlatformFactory ().asMacFactory ()->getUseAsynchronousLayerDrawing ()) - { - caLayer.drawsAsynchronously = YES; - useInvalidRects = false; - } + caLayer.drawsAsynchronously = YES; + useInvalidRects = false; } } } @@ -1080,82 +1077,53 @@ static void draggedImageEndedAtOperation (id self, SEL _cmd, NSImage* image, NSP } //----------------------------------------------------------------------------- -void NSViewFrame::drawLayer (CALayer* layer, CGContextRef ctx) +void NSViewFrame::draw (CGContextRef cgContext, CRect updateRect, double scaleFactor) { - inDraw = true; - - auto clipBoundingBoxNSRect = CGContextGetClipBoundingBox (ctx); - auto clipBoundingBox = rectFromNSRect (clipBoundingBoxNSRect); + auto device = getPlatformFactory ().getGraphicsDeviceFactory ().getDeviceForScreen ( + DefaultScreenIdentifier); + if (!device) + return; + auto cgDevice = std::static_pointer_cast (device); + auto deviceContext = std::make_shared (*cgDevice.get (), cgContext); - addDebugRedrawRect (clipBoundingBox, true); + addDebugRedrawRect (updateRect, true); - CGDrawContext drawContext (ctx, rectFromNSRect ([nsView bounds])); - drawContext.beginDraw (); + inDraw = true; + deviceContext->beginDraw (); if (useInvalidRects) { joinNearbyInvalidRects (invalidRectList, 24.); + frame->platformDrawRects (deviceContext, scaleFactor, invalidRectList.data ()); for (auto r : invalidRectList) - { - frame->platformDrawRect (&drawContext, r); addDebugRedrawRect (r, false); - } invalidRectList.clear (); } else { - frame->platformDrawRect (&drawContext, clipBoundingBox); - addDebugRedrawRect (clipBoundingBox, false); + frame->platformDrawRects (deviceContext, scaleFactor, {1, updateRect}); + addDebugRedrawRect (updateRect, false); } - drawContext.endDraw (); + deviceContext->endDraw (); inDraw = false; } +//----------------------------------------------------------------------------- +void NSViewFrame::drawLayer (CALayer* layer, CGContextRef ctx) +{ + auto clipBoundingBox = CGContextGetClipBoundingBox (ctx); + draw (ctx, rectFromNSRect (clipBoundingBox), layer.contentsScale); +} + //----------------------------------------------------------------------------- void NSViewFrame::drawRect (NSRect* rect) { if (caLayer) return; - inDraw = true; NSGraphicsContext* nsContext = [NSGraphicsContext currentContext]; - -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAX_OS_X_VERSION_10_10 - auto cgContext = static_cast ([nsContext graphicsPort]); -#else - auto cgContext = static_cast ([nsContext CGContext]); -#endif - - addDebugRedrawRect (rectFromNSRect (*rect), true); - - CGDrawContext drawContext (cgContext, rectFromNSRect ([nsView bounds])); - drawContext.beginDraw (); - - if (useInvalidRects) - { - joinNearbyInvalidRects (invalidRectList, 24.); - for (auto r : invalidRectList) - { - frame->platformDrawRect (&drawContext, r); - addDebugRedrawRect (r, false); - } - invalidRectList.clear (); - } - else - { - const NSRect* dirtyRects; - NSInteger numDirtyRects; - [nsView getRectsBeingDrawn:&dirtyRects count:&numDirtyRects]; - for (NSInteger i = 0; i < numDirtyRects; i++) - { - auto r = rectFromNSRect (dirtyRects[i]); - frame->platformDrawRect (&drawContext, r); - addDebugRedrawRect (r, false); - } - } - drawContext.endDraw (); - inDraw = false; + draw (nsContext.CGContext, rectFromNSRect (*rect), nsView.window.backingScaleFactor); } //------------------------------------------------------------------------ @@ -1314,7 +1282,7 @@ static MouseEventButtonState buttonStateFromNSEvent (NSEvent* theEvent) buttons |= kButton4; if (mouseButtons & (1 << 4)) buttons |= kButton5; - + return true; } @@ -1514,6 +1482,7 @@ static MouseEventButtonState buttonStateFromNSEvent (NSEvent* theEvent) [nsView setWantsLayer:YES]; caLayer.actions = nil; } + auto caParentLayer = parentViewLayer ? parentViewLayer->getCALayer () : (caLayer ? caLayer : nsView.layer); auto layer = makeOwned (caParentLayer); @@ -1636,7 +1605,7 @@ static MouseEventButtonState buttonStateFromNSEvent (NSEvent* theEvent) return; if (![nsView respondsToSelector:@selector(setTouchBar:)]) return; - + if (!touchBarCreator) [nsView performSelector:@selector(setTouchBar:) withObject:nil]; else diff --git a/vstgui/lib/platform/mac/coregraphicsdevicecontext.h b/vstgui/lib/platform/mac/coregraphicsdevicecontext.h new file mode 100644 index 000000000..b9241f65a --- /dev/null +++ b/vstgui/lib/platform/mac/coregraphicsdevicecontext.h @@ -0,0 +1,115 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE + +#pragma once + +#include "../iplatformgraphicsdevice.h" + +#if TARGET_OS_IPHONE +#include +#include +#else +#include +#endif + +//------------------------------------------------------------------------ +namespace VSTGUI { + +class CoreGraphicsDevice; + +//------------------------------------------------------------------------ +class CoreGraphicsDeviceContext : public IPlatformGraphicsDeviceContext, + public IPlatformGraphicsDeviceContextBitmapExt +{ +public: + CoreGraphicsDeviceContext (const CoreGraphicsDevice& device, void* cgContext); + ~CoreGraphicsDeviceContext () noexcept override; + + const IPlatformGraphicsDevice& getDevice () const override; + PlatformGraphicsPathFactoryPtr getGraphicsPathFactory () const override; + + bool beginDraw () const override; + bool endDraw () const override; + // draw commands + bool drawLine (LinePair line) const override; + bool drawLines (const LineList& lines) const override; + bool drawPolygon (const PointList& polygonPointList, + PlatformGraphicsDrawStyle drawStyle) const override; + bool drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override; + bool drawArc (CRect rect, double startAngle1, double endAngle2, + PlatformGraphicsDrawStyle drawStyle) const override; + bool drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override; + bool drawPoint (CPoint point, CColor color) const override; + bool drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, double alpha, + BitmapInterpolationQuality quality) const override; + bool clearRect (CRect rect) const override; + bool drawGraphicsPath (IPlatformGraphicsPath& path, PlatformGraphicsPathDrawMode mode, + TransformMatrix* transformation) const override; + bool fillLinearGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint startPoint, CPoint endPoint, bool evenOdd, + TransformMatrix* transformation) const override; + bool fillRadialGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint center, CCoord radius, CPoint originOffset, bool evenOdd, + TransformMatrix* transformation) const override; + // state + void saveGlobalState () const override; + void restoreGlobalState () const override; + void setLineStyle (const CLineStyle& style) const override; + void setLineWidth (CCoord width) const override; + void setDrawMode (CDrawMode mode) const override; + void setClipRect (CRect clip) const override; + void setFillColor (CColor color) const override; + void setFrameColor (CColor color) const override; + void setGlobalAlpha (double newAlpha) const override; + void setTransformMatrix (const TransformMatrix& tm) const override; + + // extension + const IPlatformGraphicsDeviceContextBitmapExt* asBitmapExt () const override; + + // IPlatformGraphicsDeviceContextBitmapExt + bool drawBitmapNinePartTiled (IPlatformBitmap& bitmap, CRect dest, + const CNinePartTiledDescription& desc, double alpha, + BitmapInterpolationQuality quality) const override; + bool fillRectWithBitmap (IPlatformBitmap& bitmap, CRect srcRect, CRect dstRect, double alpha, + BitmapInterpolationQuality quality) const override; + + // private + void drawCTLine (CTLineRef line, CGPoint cgPoint, CTFontRef fontRef, CColor color, + bool underline, bool strikeThrough, bool antialias) const; + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +class CoreGraphicsBitmapContext : public CoreGraphicsDeviceContext +{ +public: + using EndDrawFunc = std::function; + CoreGraphicsBitmapContext (const CoreGraphicsDevice& device, void* cgContext, EndDrawFunc&& f); + + bool endDraw () const override; + +private: + EndDrawFunc endDrawFunc; +}; + +//------------------------------------------------------------------------ +class CoreGraphicsDevice : public IPlatformGraphicsDevice +{ +public: + PlatformGraphicsDeviceContextPtr + createBitmapContext (const PlatformBitmapPtr& bitmap) const override; +}; + +//------------------------------------------------------------------------ +class CoreGraphicsDeviceFactory : public IPlatformGraphicsDeviceFactory +{ +public: + PlatformGraphicsDevicePtr getDeviceForScreen (ScreenInfo::Identifier screen) const override; +}; + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/mac/coregraphicsdevicecontext.mm b/vstgui/lib/platform/mac/coregraphicsdevicecontext.mm new file mode 100644 index 000000000..c98e466e1 --- /dev/null +++ b/vstgui/lib/platform/mac/coregraphicsdevicecontext.mm @@ -0,0 +1,964 @@ +// 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 "../../ccolor.h" +#include "../../cdrawdefs.h" +#include "../../cgraphicstransform.h" +#include "../../clinestyle.h" +#include "../../crect.h" +#include "cgbitmap.h" +#include "macglobals.h" +#include "coregraphicsdevicecontext.h" +#include "quartzgraphicspath.h" + +#include + +#if TARGET_OS_IPHONE +#include +#endif + +//------------------------------------------------------------------------ +namespace VSTGUI { + +//------------------------------------------------------------------------ +PlatformGraphicsDevicePtr + CoreGraphicsDeviceFactory::getDeviceForScreen (ScreenInfo::Identifier screen) const +{ + static PlatformGraphicsDevicePtr gCoreGraphicsDevice = std::make_shared (); + return gCoreGraphicsDevice; +} + +//------------------------------------------------------------------------ +static CGPathDrawingMode convert (PlatformGraphicsPathDrawMode mode) +{ + switch (mode) + { + case PlatformGraphicsPathDrawMode::FilledEvenOdd: + return kCGPathEOFill; + case PlatformGraphicsPathDrawMode::Filled: + return kCGPathFill; + case PlatformGraphicsPathDrawMode::Stroked: + return kCGPathStroke; + } + vstgui_assert (false); +} + +//------------------------------------------------------------------------ +static CGPathDrawingMode convert (PlatformGraphicsDrawStyle drawStyle) +{ + switch (drawStyle) + { + case PlatformGraphicsDrawStyle::Filled: + return kCGPathFill; + case PlatformGraphicsDrawStyle::FilledAndStroked: + return kCGPathFillStroke; + case PlatformGraphicsDrawStyle::Stroked: + return kCGPathStroke; + } + vstgui_assert (false); +} + +//------------------------------------------------------------------------ +auto CoreGraphicsDevice::createBitmapContext (const PlatformBitmapPtr& bitmap) const + -> PlatformGraphicsDeviceContextPtr +{ + auto cgBitmap = bitmap.cast (); + if (cgBitmap) + { + auto scaleFactor = bitmap->getScaleFactor (); + auto cgContext = cgBitmap->createCGContext (); + CGContextConcatCTM (cgContext, + CGAffineTransformMakeScale (static_cast (scaleFactor), + static_cast (scaleFactor))); + auto bitmapContext = std::make_shared ( + *this, cgContext, [cgBitmap = shared (cgBitmap)] () { cgBitmap->setDirty (); }); + CFRelease (cgContext); + return bitmapContext; + } + return nullptr; +} + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +struct CoreGraphicsDeviceContext::Impl +{ + //----------------------------------------------------------------------------- + Impl (const CoreGraphicsDevice& d, CGContextRef cgContext) : device (d), cgContext (cgContext) + { + CFRetain (cgContext); + + auto userRect = CGRectMake (0, 0, 100, 100); + auto deviceRect = CGContextConvertRectToDeviceSpace (cgContext, userRect); + backendScaleFactor = deviceRect.size.height / userRect.size.height; + + CGContextSaveGState (cgContext); + CGContextSetAllowsAntialiasing (cgContext, true); + CGContextSetAllowsFontSmoothing (cgContext, true); + CGContextSetAllowsFontSubpixelPositioning (cgContext, true); + CGContextSetAllowsFontSubpixelQuantization (cgContext, true); + CGContextSetShouldAntialias (cgContext, false); + CGContextSetFillColorSpace (cgContext, GetCGColorSpace ()); + CGContextSetStrokeColorSpace (cgContext, GetCGColorSpace ()); + CGContextSaveGState (cgContext); + CGAffineTransform cgCTM = CGAffineTransformMake (1.0, 0.0, 0.0, -1.0, 0.0, 0.0); + CGContextSetTextMatrix (cgContext, cgCTM); + } + + //----------------------------------------------------------------------------- + ~Impl () + { + CGContextRestoreGState (cgContext); // restore the original state + CGContextRestoreGState (cgContext); // we need to do it twice !!! + CFRelease (cgContext); + } + + //----------------------------------------------------------------------------- + template + static void DoGraphicStateSave (CGContextRef context, Proc proc) + { + CGContextSaveGState (context); + proc (); + CGContextRestoreGState (context); + } + + //------------------------------------------------------------------------ + template + void doInCGContext (bool swapYAxis, bool integralOffset, Proc proc) const + { + if (state.clipRect.isEmpty ()) + return; + + CGContextSaveGState (cgContext); + + CGRect cgClipRect = CGRectFromCRect (state.clipRect); + if (integralOffset) + cgClipRect = pixelAlligned (cgClipRect); + CGContextClipToRect (cgContext, cgClipRect); +#if DEBUG + if (showClip) + { + CGContextSetRGBFillColor (cgContext, 1, 0, 0, 0.5); + CGContextFillRect (cgContext, cgClipRect); + } +#endif + + if (state.tm.isInvariant () == false) + { + auto t = state.tm; + if (integralOffset) + { + CGPoint p = pixelAlligned (CGPointFromCPoint (CPoint (t.dx, t.dy))); + t.dx = p.x; + t.dy = p.y; + } + CGContextConcatCTM (cgContext, createCGAffineTransform (t)); + } + + if (!swapYAxis) + CGContextScaleCTM (cgContext, 1, -1); + + CGContextSetAlpha (cgContext, state.globalAlpha); + + proc (cgContext); + + CGContextRestoreGState (cgContext); + } + + //----------------------------------------------------------------------------- + CGRect pixelAlligned (const CGRect& r) const + { + CGRect result; + result.origin = CGContextConvertPointToDeviceSpace (cgContext, r.origin); + result.size = CGContextConvertSizeToDeviceSpace (cgContext, r.size); + result.origin.x = static_cast (std::round (result.origin.x)); + result.origin.y = static_cast (std::round (result.origin.y)); + result.size.width = static_cast (std::round (result.size.width)); + result.size.height = static_cast (std::round (result.size.height)); + result.origin = CGContextConvertPointToUserSpace (cgContext, result.origin); + result.size = CGContextConvertSizeToUserSpace (cgContext, result.size); + return result; + } + + //----------------------------------------------------------------------------- + CGPoint pixelAlligned (const CGPoint& p) const + { + CGPoint result = CGContextConvertPointToDeviceSpace (cgContext, p); + result.x = static_cast (std::round (result.x)); + result.y = static_cast (std::round (result.y)); + result = CGContextConvertPointToUserSpace (cgContext, result); + return result; + } + + //------------------------------------------------------------------------ + void applyLineStyle () const + { + switch (state.lineStyle.getLineCap ()) + { + case CLineStyle::kLineCapButt: + CGContextSetLineCap (cgContext, kCGLineCapButt); + break; + case CLineStyle::kLineCapRound: + CGContextSetLineCap (cgContext, kCGLineCapRound); + break; + case CLineStyle::kLineCapSquare: + CGContextSetLineCap (cgContext, kCGLineCapSquare); + break; + } + switch (state.lineStyle.getLineJoin ()) + { + case CLineStyle::kLineJoinMiter: + CGContextSetLineJoin (cgContext, kCGLineJoinMiter); + break; + case CLineStyle::kLineJoinRound: + CGContextSetLineJoin (cgContext, kCGLineJoinRound); + break; + case CLineStyle::kLineJoinBevel: + CGContextSetLineJoin (cgContext, kCGLineJoinBevel); + break; + } + if (state.lineStyle.getDashCount () > 0) + { + CGFloat* dashLengths = new CGFloat[state.lineStyle.getDashCount ()]; + for (uint32_t i = 0; i < state.lineStyle.getDashCount (); i++) + { + dashLengths[i] = + static_cast (state.lineWidth * state.lineStyle.getDashLengths ()[i]); + } + CGContextSetLineDash (cgContext, static_cast (state.lineStyle.getDashPhase ()), + dashLengths, state.lineStyle.getDashCount ()); + delete[] dashLengths; + } + } + + //----------------------------------------------------------------------------- + void applyLineWidthCTM () const + { + int32_t lineWidthInt = static_cast (state.lineWidth); + if (static_cast (lineWidthInt) == state.lineWidth && lineWidthInt % 2) + CGContextTranslateCTM (cgContext, 0.5, 0.5); + } + + //------------------------------------------------------------------------ + void setBitmapInterpolationQuality (BitmapInterpolationQuality q) const + { + switch (q) + { + case BitmapInterpolationQuality::kLow: + { + CGContextSetShouldAntialias (cgContext, false); + CGContextSetInterpolationQuality (cgContext, kCGInterpolationNone); + break; + } + case BitmapInterpolationQuality::kMedium: + { + CGContextSetShouldAntialias (cgContext, true); + CGContextSetInterpolationQuality (cgContext, kCGInterpolationMedium); + break; + } + case BitmapInterpolationQuality::kHigh: + { + CGContextSetShouldAntialias (cgContext, true); + CGContextSetInterpolationQuality (cgContext, kCGInterpolationHigh); + break; + } + case BitmapInterpolationQuality::kDefault: + { + CGContextSetShouldAntialias (cgContext, true); + CGContextSetInterpolationQuality (cgContext, kCGInterpolationDefault); + break; + } + default: + vstgui_assert (false); + break; + } + } + + //----------------------------------------------------------------------------- + void drawCGImageRef (CGContextRef context, CGImageRef image, CGLayerRef layer, + double bitmapScaleFactor, const CRect& inRect, const CPoint& inOffset, + float alpha) const + { + CRect rect (inRect); + CPoint offset (inOffset); + CGSize bitmapNormSize; + if (layer) + { + bitmapNormSize = CGLayerGetSize (layer); + } + else + { + bitmapNormSize.width = CGImageGetWidth (image); + bitmapNormSize.height = CGImageGetHeight (image); + } + bitmapNormSize.width /= bitmapScaleFactor; + bitmapNormSize.height /= bitmapScaleFactor; + + CGContextSetAlpha (context, (CGFloat)alpha * state.globalAlpha); + + CGRect dest; + dest.origin.x = static_cast (rect.left - offset.x); + dest.origin.y = static_cast (-(rect.top) - (bitmapNormSize.height - offset.y)); + dest.size = bitmapNormSize; + + CGRect cgClipRect; + cgClipRect.origin.x = static_cast (rect.left); + cgClipRect.origin.y = static_cast (-(rect.top) - rect.getHeight ()); + cgClipRect.size.width = static_cast (rect.getWidth ()); + cgClipRect.size.height = static_cast (rect.getHeight ()); + + if (bitmapScaleFactor != 1.) + { + CGContextConcatCTM (context, CGAffineTransformMakeScale ( + static_cast (1. / bitmapScaleFactor), + static_cast (1. / bitmapScaleFactor))); + CGAffineTransform transform = CGAffineTransformMakeScale ( + static_cast (bitmapScaleFactor), static_cast (bitmapScaleFactor)); + cgClipRect.origin = CGPointApplyAffineTransform (cgClipRect.origin, transform); + cgClipRect.size = CGSizeApplyAffineTransform (cgClipRect.size, transform); + dest.origin = CGPointApplyAffineTransform (dest.origin, transform); + dest.size = CGSizeApplyAffineTransform (dest.size, transform); + } + cgClipRect.origin = pixelAlligned (cgClipRect.origin); + + CGContextClipToRect (context, cgClipRect); + + if (layer) + { + CGContextDrawLayerInRect (context, dest, layer); + } + else + { + CGContextDrawImage (context, dest, image); + } + } + + //------------------------------------------------------------------------ + const CoreGraphicsDevice& device; + CGContextRef cgContext {nullptr}; + + struct State + { + CLineStyle lineStyle {kLineSolid}; + CCoord lineWidth {1}; + CDrawMode drawMode {}; + CRect clipRect {}; + double globalAlpha {1.}; + TransformMatrix tm {}; + }; + + State state; + std::stack stateStack; + + double backendScaleFactor {1.}; + + using BitmapDrawCountMap = std::map; + BitmapDrawCountMap bitmapDrawCount; + +#if defined(VSTGUI_TEXTRENDERING_LEGACY_INCONSISTENCY) && \ + VSTGUI_TEXTRENDERING_LEGACY_INCONSISTENCY == 1 + static constexpr bool shouldSmoothFonts = true; +#else + static constexpr bool shouldSmoothFonts = false; +#endif + +#if DEBUG + bool showClip {false}; +#endif +}; + +//------------------------------------------------------------------------ +CoreGraphicsDeviceContext::CoreGraphicsDeviceContext (const CoreGraphicsDevice& device, + void* cgContext) +{ + impl = std::make_unique (device, static_cast (cgContext)); +} + +//------------------------------------------------------------------------ +CoreGraphicsDeviceContext::~CoreGraphicsDeviceContext () noexcept {} + +//------------------------------------------------------------------------ +const IPlatformGraphicsDevice& CoreGraphicsDeviceContext::getDevice () const +{ + return impl->device; +} + +//------------------------------------------------------------------------ +PlatformGraphicsPathFactoryPtr CoreGraphicsDeviceContext::getGraphicsPathFactory () const +{ + return CGGraphicsPathFactory::instance (); +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::beginDraw () const { return true; } + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::endDraw () const +{ + impl->bitmapDrawCount.clear (); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawLine (LinePair line) const +{ + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + impl->applyLineStyle (); + + CGContextBeginPath (context); + CGPoint first = CGPointFromCPoint (line.first); + CGPoint second = CGPointFromCPoint (line.second); + + if (impl->state.drawMode.integralMode ()) + { + first = impl->pixelAlligned (first); + second = impl->pixelAlligned (second); + impl->applyLineWidthCTM (); + } + + CGContextMoveToPoint (context, first.x, first.y); + CGContextAddLineToPoint (context, second.x, second.y); + + CGContextDrawPath (context, kCGPathStroke); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawLines (const LineList& lines) const +{ + vstgui_assert (lines.empty () == false); + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + impl->applyLineStyle (); + static constexpr auto numStackPoints = 32; + CGPoint stackPoints[numStackPoints]; + + auto cgPointsPtr = (lines.size () * 2) < numStackPoints + ? nullptr + : std::unique_ptr (new CGPoint[lines.size () * 2]); + auto cgPoints = cgPointsPtr ? cgPointsPtr.get () : stackPoints; + uint32_t index = 0; + if (impl->state.drawMode.integralMode ()) + { + for (const auto& line : lines) + { + cgPoints[index] = impl->pixelAlligned (CGPointFromCPoint (line.first)); + cgPoints[index + 1] = impl->pixelAlligned (CGPointFromCPoint (line.second)); + index += 2; + } + } + else + { + for (const auto& line : lines) + { + cgPoints[index] = CGPointFromCPoint (line.first); + cgPoints[index + 1] = CGPointFromCPoint (line.second); + index += 2; + } + } + + if (impl->state.drawMode.integralMode ()) + impl->applyLineWidthCTM (); + + const size_t maxPointsPerIteration = 16; + const CGPoint* pointPtr = cgPoints; + size_t numPoints = lines.size () * 2; + while (numPoints) + { + size_t np = std::min (numPoints, std::min (maxPointsPerIteration, numPoints)); + CGContextStrokeLineSegments (context, pointPtr, np); + numPoints -= np; + pointPtr += np; + } + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawPolygon (const PointList& polygonPointList, + PlatformGraphicsDrawStyle drawStyle) const +{ + vstgui_assert (polygonPointList.empty () == false); + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + CGPathDrawingMode m = convert (drawStyle); + impl->applyLineStyle (); + + CGContextBeginPath (context); + CGPoint p = CGPointFromCPoint (polygonPointList[0]); + if (impl->state.drawMode.integralMode ()) + p = impl->pixelAlligned (p); + CGContextMoveToPoint (context, p.x, p.y); + for (uint32_t i = 1; i < polygonPointList.size (); i++) + { + p = CGPointFromCPoint (polygonPointList[i]); + if (impl->state.drawMode.integralMode ()) + p = impl->pixelAlligned (p); + CGContextAddLineToPoint (context, p.x, p.y); + } + CGContextDrawPath (context, m); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + CGRect r = CGRectFromCRect (rect); + if (drawStyle != PlatformGraphicsDrawStyle::Filled) + { + r.size.width -= 1.; + r.size.height -= 1.; + } + + CGPathDrawingMode m = convert (drawStyle); + impl->applyLineStyle (); + + if (impl->state.drawMode.integralMode ()) + { + r = impl->pixelAlligned (r); + if (drawStyle != PlatformGraphicsDrawStyle::Filled) + impl->applyLineWidthCTM (); + } + + CGContextBeginPath (context); + CGContextAddRect (context, r); + CGContextDrawPath (context, m); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawArc (CRect rect, double startAngle1, double endAngle2, + PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + CGPathDrawingMode m = convert (drawStyle); + impl->applyLineStyle (); + + CGContextBeginPath (context); + + CPoint center (rect.left + rect.getWidth () / 2., rect.top + rect.getHeight () / 2.); + auto rX = rect.getWidth () / 2.; + auto rY = rect.getHeight () / 2.; + + Impl::DoGraphicStateSave (context, [&] () { + CGContextTranslateCTM (context, static_cast (center.x), + static_cast (center.y)); + CGContextScaleCTM (context, rX, rY); + + auto startAngle = radians (startAngle1); + auto endAngle = radians (endAngle2); + if (rX != rY) + { + startAngle = std::atan2 (std::sin (startAngle) * rX, std::cos (startAngle) * rY); + endAngle = std::atan2 (std::sin (endAngle) * rX, std::cos (endAngle) * rY); + } + CGContextMoveToPoint (context, static_cast (std::cos (startAngle)), + static_cast (std::sin (startAngle))); + CGContextAddArc (context, 0, 0, 1, static_cast (startAngle), + static_cast (endAngle), 0); + }); + CGContextDrawPath (context, m); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + CGRect r = CGRectFromCRect (rect); + if (drawStyle != PlatformGraphicsDrawStyle::Filled) + { + r.size.width -= 1.; + r.size.height -= 1.; + } + + CGPathDrawingMode m = convert (drawStyle); + impl->applyLineStyle (); + if (impl->state.drawMode.integralMode ()) + { + if (drawStyle != PlatformGraphicsDrawStyle::Filled) + impl->applyLineWidthCTM (); + r = impl->pixelAlligned (r); + } + + CGContextAddEllipseInRect (context, r); + CGContextDrawPath (context, m); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawPoint (CPoint point, CColor color) const { return false; } + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, + double alpha, BitmapInterpolationQuality quality) const +{ + auto cgBitmap = dynamic_cast (&bitmap); + if (!cgBitmap) + return false; + auto cgImage = cgBitmap->getCGImage (); + if (!cgImage) + return false; + impl->doInCGContext (false, true, [&] (auto context) { + impl->setBitmapInterpolationQuality (quality); + + CGLayerRef layer = nullptr; + if (impl->backendScaleFactor == 1.) + { + layer = cgBitmap->getCGLayer (); + if (layer == nullptr) + { + auto it = impl->bitmapDrawCount.find (cgBitmap); + if (it == impl->bitmapDrawCount.end ()) + { + impl->bitmapDrawCount.emplace (cgBitmap, 1); + } + else + { + it->second++; + layer = cgBitmap->createCGLayer (context); + } + } + } + + impl->drawCGImageRef (context, cgImage, layer, cgBitmap->getScaleFactor (), dest, offset, + alpha); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::clearRect (CRect rect) const +{ + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + CGRect cgRect = CGRectFromCRect (rect); + if (impl->state.drawMode.integralMode ()) + cgRect = impl->pixelAlligned (cgRect); + CGContextClearRect (context, cgRect); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawGraphicsPath (IPlatformGraphicsPath& path, + PlatformGraphicsPathDrawMode mode, + TransformMatrix* transformation) const +{ + auto cgPath = dynamic_cast (&path); + if (!cgPath) + return false; + + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + auto cgMode = convert (mode); + if (cgMode == kCGPathStroke) + impl->applyLineStyle (); + Impl::DoGraphicStateSave (context, [&] () { + if (transformation) + { + CGAffineTransform transform = createCGAffineTransform (*transformation); + CGContextConcatCTM (context, transform); + } + if (impl->state.drawMode.integralMode () && impl->state.drawMode.aliasing ()) + { + Impl::DoGraphicStateSave (context, [&] () { + impl->applyLineWidthCTM (); + cgPath->pixelAlign ( + [] (const CGPoint& p, void* context) { + auto devContext = reinterpret_cast (context); + return devContext->pixelAlligned (p); + }, + impl.get ()); + }); + CGContextAddPath (context, cgPath->getCGPathRef ()); + } + else + CGContextAddPath (context, cgPath->getCGPathRef ()); + }); + CGContextDrawPath (context, cgMode); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::fillLinearGradient (IPlatformGraphicsPath& path, + const IPlatformGradient& gradient, + CPoint startPoint, CPoint endPoint, + bool evenOdd, + TransformMatrix* transformation) const +{ + auto cgPath = dynamic_cast (&path); + if (!cgPath) + return false; + auto cgGradient = dynamic_cast (&gradient); + if (!cgGradient) + return false; + + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + CGPoint start = CGPointFromCPoint (startPoint); + CGPoint end = CGPointFromCPoint (endPoint); + Impl::DoGraphicStateSave (context, [&] () { + if (impl->state.drawMode.integralMode ()) + { + start = impl->pixelAlligned (start); + end = impl->pixelAlligned (end); + } + if (transformation) + { + CGAffineTransform transform = createCGAffineTransform (*transformation); + CGContextConcatCTM (context, transform); + } + if (impl->state.drawMode.integralMode () && impl->state.drawMode.aliasing ()) + { + cgPath->pixelAlign ( + [] (const CGPoint& p, void* context) { + auto devContext = reinterpret_cast (context); + return devContext->pixelAlligned (p); + }, + impl.get ()); + } + + CGContextAddPath (context, cgPath->getCGPathRef ()); + }); + + if (evenOdd) + CGContextEOClip (context); + else + CGContextClip (context); + + CGContextDrawLinearGradient (context, *cgGradient, start, end, + kCGGradientDrawsBeforeStartLocation | + kCGGradientDrawsAfterEndLocation); + }); + return true; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::fillRadialGradient (IPlatformGraphicsPath& path, + const IPlatformGradient& gradient, + CPoint center, CCoord radius, + CPoint originOffset, bool evenOdd, + TransformMatrix* transformation) const +{ + auto cgPath = dynamic_cast (&path); + if (!cgPath) + return false; + auto cgGradient = dynamic_cast (&gradient); + if (!cgGradient) + return false; + + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + Impl::DoGraphicStateSave (context, [&] () { + if (transformation) + { + CGAffineTransform transform = createCGAffineTransform (*transformation); + CGContextConcatCTM (context, transform); + } + if (impl->state.drawMode.integralMode () && impl->state.drawMode.aliasing ()) + { + cgPath->pixelAlign ( + [] (const CGPoint& p, void* context) { + auto devContext = reinterpret_cast (context); + return devContext->pixelAlligned (p); + }, + impl.get ()); + } + + CGContextAddPath (context, cgPath->getCGPathRef ()); + }); + + if (evenOdd) + CGContextEOClip (context); + else + CGContextClip (context); + + CPoint startCenter = center + originOffset; + CGContextDrawRadialGradient (context, *cgGradient, CGPointFromCPoint (startCenter), 0, + CGPointFromCPoint (center), static_cast (radius), + kCGGradientDrawsBeforeStartLocation | + kCGGradientDrawsAfterEndLocation); + }); + return true; +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::saveGlobalState () const +{ + CGContextSaveGState (impl->cgContext); + impl->stateStack.push (impl->state); +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::restoreGlobalState () const +{ + vstgui_assert (impl->stateStack.empty () == false, + "Unbalanced calls to saveGlobalState and restoreGlobalState"); +#if NDEBUG + if (impl->stateStack.empty ()) + return; +#endif + CGContextRestoreGState (impl->cgContext); + impl->state = impl->stateStack.top (); + impl->stateStack.pop (); +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setLineStyle (const CLineStyle& style) const +{ + impl->state.lineStyle = style; +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setLineWidth (CCoord width) const +{ + impl->state.lineWidth = width; + CGContextSetLineWidth (impl->cgContext, static_cast (width)); +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setDrawMode (CDrawMode mode) const +{ + impl->state.drawMode = mode; + CGContextSetShouldAntialias (impl->cgContext, mode.antiAliasing ()); +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setClipRect (CRect clip) const { impl->state.clipRect = clip; } + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setFillColor (CColor color) const +{ + CGContextSetFillColorWithColor (impl->cgContext, getCGColor (color)); +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setFrameColor (CColor color) const +{ + CGContextSetStrokeColorWithColor (impl->cgContext, getCGColor (color)); +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setGlobalAlpha (double newAlpha) const +{ + impl->state.globalAlpha = newAlpha; +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::setTransformMatrix (const TransformMatrix& tm) const +{ + impl->state.tm = tm; +} + +//------------------------------------------------------------------------ +const IPlatformGraphicsDeviceContextBitmapExt* CoreGraphicsDeviceContext::asBitmapExt () const +{ + return nullptr; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::drawBitmapNinePartTiled (IPlatformBitmap& bitmap, CRect dest, + const CNinePartTiledDescription& desc, + double alpha, + BitmapInterpolationQuality quality) const +{ + // Not Supported + return false; +} + +//------------------------------------------------------------------------ +bool CoreGraphicsDeviceContext::fillRectWithBitmap (IPlatformBitmap& bitmap, CRect srcRect, + CRect dstRect, double alpha, + BitmapInterpolationQuality quality) const +{ +#if 0 + auto cgBitmap = dynamic_cast (&bitmap); + if (!cgBitmap) + return false; + auto cgImage = cgBitmap->getCGImage (); + if (cgImage) + return false; + impl->doInCGContext (false, true, [&] (auto context) { + impl->setBitmapInterpolationQuality (quality); + + // TODO: Check if this works with retina images + + CGRect clipRect = CGRectFromCRect (dstRect); + clipRect.origin.y = -(clipRect.origin.y) - clipRect.size.height; + clipRect = impl->pixelAlligned (clipRect); + CGContextClipToRect (context, clipRect); + + CGRect r = {}; + r.size.width = CGImageGetWidth (cgImage); + r.size.height = CGImageGetHeight (cgImage); + + CGContextDrawTiledImage (context, r, cgImage); + }); +#endif + return false; +} + +//------------------------------------------------------------------------ +void CoreGraphicsDeviceContext::drawCTLine (CTLineRef line, CGPoint cgPoint, CTFontRef fontRef, + CColor color, bool underline, bool strikeThrough, + bool antialias) const +{ + impl->doInCGContext (true, impl->state.drawMode.integralMode (), [&] (auto context) { + if (impl->state.drawMode.integralMode ()) + cgPoint = impl->pixelAlligned (cgPoint); + CGContextSetShouldAntialias (context, antialias); + CGContextSetShouldSmoothFonts (context, impl->shouldSmoothFonts); + CGContextSetShouldSubpixelPositionFonts (context, true); + CGContextSetShouldSubpixelQuantizeFonts (context, true); + CGContextSetTextPosition (context, cgPoint.x, cgPoint.y); + CTLineDraw (line, context); + CGColorRef cgColorRef = nullptr; + if (underline) + { + cgColorRef = getCGColor (color); + CGFloat underlineOffset = CTFontGetUnderlinePosition (fontRef) - 1.f; + CGFloat underlineThickness = CTFontGetUnderlineThickness (fontRef); + CGContextSetStrokeColorWithColor (context, cgColorRef); + CGContextSetLineWidth (context, underlineThickness); + auto cgPoint2 = CGContextGetTextPosition (context); + CGContextBeginPath (context); + CGContextMoveToPoint (context, cgPoint.x, cgPoint.y - underlineOffset); + CGContextAddLineToPoint (context, cgPoint2.x, cgPoint.y - underlineOffset); + CGContextDrawPath (context, kCGPathStroke); + } + if (strikeThrough) + { + if (!cgColorRef) + cgColorRef = getCGColor (color); + CGFloat underlineThickness = CTFontGetUnderlineThickness (fontRef); + CGFloat offset = CTFontGetXHeight (fontRef) * 0.5f; + CGContextSetStrokeColorWithColor (context, cgColorRef); + CGContextSetLineWidth (context, underlineThickness); + auto cgPoint2 = CGContextGetTextPosition (context); + CGContextBeginPath (context); + CGContextMoveToPoint (context, cgPoint.x, cgPoint.y - offset); + CGContextAddLineToPoint (context, cgPoint2.x, cgPoint.y - offset); + CGContextDrawPath (context, kCGPathStroke); + } + }); +} + +//------------------------------------------------------------------------ +CoreGraphicsBitmapContext::CoreGraphicsBitmapContext (const CoreGraphicsDevice& device, + void* cgContext, EndDrawFunc&& f) +: CoreGraphicsDeviceContext (device, cgContext), endDrawFunc (std::move (f)) +{ +} + +//------------------------------------------------------------------------ +bool CoreGraphicsBitmapContext::endDraw () const +{ + endDrawFunc (); + return CoreGraphicsDeviceContext::endDraw (); +} + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/mac/ios/uiviewframe.mm b/vstgui/lib/platform/mac/ios/uiviewframe.mm index 4b225f32b..03565322a 100644 --- a/vstgui/lib/platform/mac/ios/uiviewframe.mm +++ b/vstgui/lib/platform/mac/ios/uiviewframe.mm @@ -8,7 +8,7 @@ #import "../../../cfileselector.h" #import "../../../idatapackage.h" #import "../../iplatformoptionmenu.h" -#import "../cgdrawcontext.h" +#import "../coregraphicsdevicecontext.h" #import "../cgbitmap.h" #import "../quartzgraphicspath.h" #import "../caviewlayer.h" @@ -55,8 +55,16 @@ - (id)initWithUIViewFrame:(UIViewFrame*)viewFrame parent:(UIView*)parent size:(c //----------------------------------------------------------------------------- - (void)drawRect:(CGRect)rect { - CGDrawContext drawContext (UIGraphicsGetCurrentContext (), CRectFromCGRect (self.bounds)); - uiViewFrame->getFrame ()->platformDrawRect (&drawContext, CRectFromCGRect (rect)); + auto device = getPlatformFactory ().getGraphicsDeviceFactory ().getDeviceForScreen ( + DefaultScreenIdentifier); + if (!device) + return; + auto cgDevice = std::static_pointer_cast (device); + auto deviceContext = std::make_shared ( + *cgDevice.get (), UIGraphicsGetCurrentContext ()); + + uiViewFrame->getFrame ()->platformDrawRects (deviceContext, self.layer.contentsScale, + {CRectFromCGRect (rect)}); } //----------------------------------------------------------------------------- diff --git a/vstgui/lib/platform/mac/macfactory.h b/vstgui/lib/platform/mac/macfactory.h index 3ed621fa2..e77d04ea8 100644 --- a/vstgui/lib/platform/mac/macfactory.h +++ b/vstgui/lib/platform/mac/macfactory.h @@ -115,14 +115,6 @@ class MacFactory final : public IPlatformFactory */ DataPackagePtr getClipboard () const noexcept final; - /** create an offscreen draw device - * @param size the size of the bitmap where the offscreen renders to - * @param scaleFactor the scale factor for drawing - * @return an offscreen context object or nullptr on failure - */ - COffscreenContextPtr createOffscreenContext (const CPoint& size, - double scaleFactor = 1.) const noexcept final; - /** Create a platform gradient object * @return platform gradient object or nullptr on failure */ @@ -136,6 +128,12 @@ class MacFactory final : public IPlatformFactory PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style, IPlatformFrame* frame) const noexcept final; + /** Get the graphics device factory + * + * @return platform graphics device factory + */ + const IPlatformGraphicsDeviceFactory& getGraphicsDeviceFactory () const noexcept final; + const LinuxFactory* asLinuxFactory () const noexcept final; const MacFactory* asMacFactory () const noexcept final; const Win32Factory* asWin32Factory () const noexcept final; diff --git a/vstgui/lib/platform/mac/macfactory.mm b/vstgui/lib/platform/mac/macfactory.mm index 298194c58..474a55d28 100644 --- a/vstgui/lib/platform/mac/macfactory.mm +++ b/vstgui/lib/platform/mac/macfactory.mm @@ -11,8 +11,8 @@ #include "../iplatformtimer.h" #include "cfontmac.h" #include "cgbitmap.h" -#include "cgdrawcontext.h" #include "quartzgraphicspath.h" +#include "coregraphicsdevicecontext.h" #include "cocoa/nsviewframe.h" #include "ios/uiviewframe.h" #include "macclipboard.h" @@ -35,6 +35,7 @@ CFBundleRef bundle {nullptr}; bool useAsynchronousLayerDrawing {true}; bool visualizeRedrawAreas {false}; + CoreGraphicsDeviceFactory graphicsDeviceFactory; }; //----------------------------------------------------------------------------- @@ -210,18 +211,6 @@ #endif } -//------------------------------------------------------------------------ -auto MacFactory::createOffscreenContext (const CPoint& size, double scaleFactor) const noexcept - -> COffscreenContextPtr -{ - auto bitmap = makeOwned (size * scaleFactor); - bitmap->setScaleFactor (scaleFactor); - auto context = makeOwned (bitmap); - if (context->getCGContext ()) - return std::move (context); - return nullptr; -} - //----------------------------------------------------------------------------- PlatformGradientPtr MacFactory::createGradient () const noexcept { @@ -239,6 +228,12 @@ return nullptr; } +//----------------------------------------------------------------------------- +const IPlatformGraphicsDeviceFactory& MacFactory::getGraphicsDeviceFactory () const noexcept +{ + return impl->graphicsDeviceFactory; +} + //----------------------------------------------------------------------------- const LinuxFactory* MacFactory::asLinuxFactory () const noexcept { diff --git a/vstgui/lib/platform/mac/mactimer.cpp b/vstgui/lib/platform/mac/mactimer.cpp index 884bd8544..d4f01bcc7 100644 --- a/vstgui/lib/platform/mac/mactimer.cpp +++ b/vstgui/lib/platform/mac/mactimer.cpp @@ -32,7 +32,7 @@ bool MacTimer::start (uint32_t fireTime) if (timer) { #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_8 - #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAX_OS_X_VERSION_10_8 + #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_8 if (CFRunLoopTimerSetTolerance) #endif CFRunLoopTimerSetTolerance (timer, fireTime * 0.0001f); diff --git a/vstgui/lib/platform/mac/quartzgraphicspath.cpp b/vstgui/lib/platform/mac/quartzgraphicspath.cpp index 8b0638df8..a5731becc 100644 --- a/vstgui/lib/platform/mac/quartzgraphicspath.cpp +++ b/vstgui/lib/platform/mac/quartzgraphicspath.cpp @@ -7,7 +7,6 @@ #if MAC #include "cfontmac.h" -#include "cgdrawcontext.h" #include "../../cgradient.h" diff --git a/vstgui/lib/platform/platformfactory.h b/vstgui/lib/platform/platformfactory.h index 45822cc08..e6673cbc7 100644 --- a/vstgui/lib/platform/platformfactory.h +++ b/vstgui/lib/platform/platformfactory.h @@ -33,7 +33,6 @@ class IPlatformFactory { public: using DataPackagePtr = SharedPointer; - using COffscreenContextPtr = SharedPointer; virtual ~IPlatformFactory () noexcept = default; @@ -130,14 +129,6 @@ class IPlatformFactory */ virtual DataPackagePtr getClipboard () const noexcept = 0; - /** create an offscreen draw device - * @param size the size of the bitmap where the offscreen renders to - * @param scaleFactor the scale factor for drawing - * @return an offscreen context object or nullptr on failure - */ - virtual COffscreenContextPtr - createOffscreenContext (const CPoint& size, double scaleFactor = 1.) const noexcept = 0; - /** Create a platform gradient object * @return platform gradient object or nullptr on failure */ @@ -151,6 +142,12 @@ class IPlatformFactory virtual PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style, IPlatformFrame* frame) const noexcept = 0; + /** Get the graphics device factory + * + * @return platform graphics device factory + */ + virtual const IPlatformGraphicsDeviceFactory& getGraphicsDeviceFactory () const noexcept = 0; + virtual const LinuxFactory* asLinuxFactory () const noexcept = 0; virtual const MacFactory* asMacFactory () const noexcept = 0; virtual const Win32Factory* asWin32Factory () const noexcept = 0; diff --git a/vstgui/lib/platform/win32/direct2d/d2d.h b/vstgui/lib/platform/win32/direct2d/d2d.h new file mode 100644 index 000000000..b8514340a --- /dev/null +++ b/vstgui/lib/platform/win32/direct2d/d2d.h @@ -0,0 +1,80 @@ +// 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 "../../../cgraphicstransform.h" +#include "../../../ccolor.h" + +#include + +//------------------------------------------------------------------------ +namespace VSTGUI { + +namespace { + +using TransformMatrix = CGraphicsTransform; + +//----------------------------------------------------------------------------- +template +void pixelAlign (const TransformMatrix& tm, T& obj) +{ + CGraphicsTransform tInv = tm.inverse (); + tm.transform (obj); + obj.makeIntegral (); + tInv.transform (obj); +} + +//------------------------------------------------------------------------ +inline D2D1_POINT_2F convert (const CPoint& p) +{ + D2D1_POINT_2F dp = {(FLOAT)p.x, (FLOAT)p.y}; + return dp; +} + +//------------------------------------------------------------------------ +inline D2D1::ColorF convert (CColor c, double alpha) +{ + return D2D1::ColorF (c.normRed (), c.normGreen (), c.normBlue (), + static_cast (c.normAlpha () * alpha)); +} + +//------------------------------------------------------------------------ +inline D2D1_RECT_F convert (const CRect& r) +{ + D2D1_RECT_F dr = {(FLOAT)r.left, (FLOAT)r.top, (FLOAT)r.right, (FLOAT)r.bottom}; + return dr; +} + +//------------------------------------------------------------------------ +inline D2D1_MATRIX_3X2_F convert (const TransformMatrix& t) +{ + D2D1_MATRIX_3X2_F matrix; + matrix._11 = static_cast (t.m11); + matrix._12 = static_cast (t.m21); + matrix._21 = static_cast (t.m12); + matrix._22 = static_cast (t.m22); + matrix._31 = static_cast (t.dx); + matrix._32 = static_cast (t.dy); + return matrix; +} + +//------------------------------------------------------------------------ +inline TransformMatrix convert (const D2D1_MATRIX_3X2_F& t) +{ + TransformMatrix matrix; + matrix.m11 = static_cast (t._11); + matrix.m21 = static_cast (t._12); + matrix.m12 = static_cast (t._21); + matrix.m22 = static_cast (t._22); + matrix.dx = static_cast (t._31); + matrix.dy = static_cast (t._32); + return matrix; +} + +//------------------------------------------------------------------------ +} // anonymous namespace + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp b/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp index ab4057aa9..ddea3106e 100644 --- a/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp +++ b/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp @@ -233,7 +233,7 @@ HBITMAP D2DBitmap::createHBitmap () pbmi.bmiHeader.biPlanes = 1; pbmi.bmiHeader.biCompression = BI_RGB; pbmi.bmiHeader.biWidth = (LONG)size.x; - pbmi.bmiHeader.biHeight = (LONG)size.y; + pbmi.bmiHeader.biHeight = -(LONG)size.y; pbmi.bmiHeader.biBitCount = 32; HDC hdc = GetDC (nullptr); diff --git a/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.cpp b/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.cpp deleted file mode 100644 index bb3f593b6..000000000 --- a/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.cpp +++ /dev/null @@ -1,879 +0,0 @@ -// 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 "d2ddrawcontext.h" - -#if WINDOWS - -#include "../win32support.h" -#include "../win32factory.h" -#include "../../../cgradient.h" -#include "d2dbitmap.h" -#include "d2dbitmapcache.h" -#include "d2dgraphicspath.h" -#include "d2dfont.h" -#include "d2dgradient.h" -#include - -namespace VSTGUI { - -//----------------------------------------------------------------------------- -D2DDrawContext::D2DApplyClip::D2DApplyClip (D2DDrawContext* drawContext, bool halfPointOffset) -: drawContext (drawContext) -{ - CGraphicsTransform transform = drawContext->getCurrentTransform (); - auto scale = drawContext->getScaleFactor (); - transform.scale (scale, scale); - if (halfPointOffset) - { - CPoint offset (0.5, 0.5); - transform.translate (offset); - } - if (transform.m12 != 0. || transform.m21 != 0.) - { // we have a rotated matrix, we need to use a layer - layerIsUsed = true; - if (drawContext->currentClip.isEmpty () == false) - drawContext->getRenderTarget ()->PopAxisAlignedClip (); - - ID2D1RectangleGeometry* geometry; - if (FAILED (getD2DFactory ()->CreateRectangleGeometry (makeD2DRect (drawContext->getCurrentState ().clipRect), &geometry))) - return; - auto d2dMatrix = convert (transform); - drawContext->getRenderTarget ()->PushLayer ( - D2D1::LayerParameters (D2D1::InfiniteRect (), geometry, - D2D1_ANTIALIAS_MODE_ALIASED), - nullptr); - drawContext->getRenderTarget ()->SetTransform (d2dMatrix); - geometry->Release (); - applyClip = drawContext->getCurrentState ().clipRect; - drawContext->currentClip = {}; - } - else - { - if (drawContext->currentClip != drawContext->getCurrentState ().clipRect) - { - CRect clip = drawContext->getCurrentState ().clipRect; - if (drawContext->currentClip.isEmpty () == false) - drawContext->getRenderTarget ()->PopAxisAlignedClip (); - if (clip.isEmpty () == false) - drawContext->getRenderTarget ()->PushAxisAlignedClip (makeD2DRect (clip), D2D1_ANTIALIAS_MODE_ALIASED); - drawContext->currentClip = applyClip = clip; - } - else - { - applyClip = drawContext->currentClip; - } - drawContext->getRenderTarget ()->SetTransform (convert (transform)); - } -} - -//----------------------------------------------------------------------------- -D2DDrawContext::D2DApplyClip::~D2DApplyClip () -{ - if (layerIsUsed) - { - drawContext->getRenderTarget ()->PopLayer (); - } - auto scale = drawContext->getScaleFactor (); - CGraphicsTransform transform; - transform.scale (scale, scale); - drawContext->getRenderTarget ()->SetTransform (convert (transform)); -} - -//----------------------------------------------------------------------------- -D2DDrawContext::D2DDrawContext (HWND window, const CRect& drawSurface) -: COffscreenContext (drawSurface) -, window (window) -, renderTarget (nullptr) -, fillBrush (nullptr) -, strokeBrush (nullptr) -, fontBrush (nullptr) -, strokeStyle (nullptr) -{ - createRenderTarget (); -} - -//----------------------------------------------------------------------------- -D2DDrawContext::D2DDrawContext (D2DBitmap* inBitmap) -: COffscreenContext (new CBitmap (inBitmap)) -, window (nullptr) -, renderTarget (nullptr) -, fillBrush (nullptr) -, strokeBrush (nullptr) -, fontBrush (nullptr) -, strokeStyle (nullptr) -, scaleFactor (inBitmap->getScaleFactor ()) -{ - createRenderTarget (); - bitmap->forget (); -} - -//----------------------------------------------------------------------------- -D2DDrawContext::D2DDrawContext (ID2D1DeviceContext* deviceContext, const CRect& drawSurface, - ID2D1Device* device) -: COffscreenContext (drawSurface) -, window (nullptr) -, device (device) -, renderTarget (nullptr) -, fillBrush (nullptr) -, strokeBrush (nullptr) -, fontBrush (nullptr) -, strokeStyle (nullptr) -{ - renderTarget = deviceContext; - renderTarget->AddRef (); - init (); -} - -//----------------------------------------------------------------------------- -D2DDrawContext::~D2DDrawContext () -{ - releaseRenderTarget (); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::createRenderTarget () -{ - if (window) - { - D2D1_RENDER_TARGET_TYPE renderTargetType = D2D1_RENDER_TARGET_TYPE_SOFTWARE; - if (auto pf = getPlatformFactory ().asWin32Factory ()) - { - renderTargetType = pf->useD2DHardwareRenderer () ? D2D1_RENDER_TARGET_TYPE_HARDWARE : - D2D1_RENDER_TARGET_TYPE_SOFTWARE; - } - RECT rc; - GetClientRect (window, &rc); - - D2D1_SIZE_U size = D2D1::SizeU (static_cast (rc.right - rc.left), static_cast (rc.bottom - rc.top)); - ID2D1HwndRenderTarget* hwndRenderTarget = nullptr; - D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat (DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED); - HRESULT hr = getD2DFactory ()->CreateHwndRenderTarget (D2D1::RenderTargetProperties (renderTargetType, pixelFormat), D2D1::HwndRenderTargetProperties (window, size, D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS), &hwndRenderTarget); - if (SUCCEEDED (hr)) - { - renderTarget = hwndRenderTarget; - renderTarget->SetDpi (96, 96); - } - } - else if (bitmap) - { - D2DBitmap* d2dBitmap = dynamic_cast (bitmap->getPlatformBitmap ().get ()); - if (d2dBitmap) - { - D2D1_RENDER_TARGET_TYPE targetType = D2D1_RENDER_TARGET_TYPE_SOFTWARE; - D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat (DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED); - getD2DFactory ()->CreateWicBitmapRenderTarget (d2dBitmap->getBitmap (), D2D1::RenderTargetProperties (targetType, pixelFormat), &renderTarget); - } - } - vstgui_assert (renderTarget); - init (); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::releaseRenderTarget () -{ - if (fillBrush) - { - fillBrush->Release (); - fillBrush = nullptr; - } - if (strokeBrush) - { - strokeBrush->Release (); - strokeBrush = nullptr; - } - if (fontBrush) - { - fontBrush->Release (); - fontBrush = nullptr; - } - if (strokeStyle) - { - strokeStyle->Release (); - strokeStyle = nullptr; - } - if (renderTarget) - { - if (!device) - D2DBitmapCache::removeRenderTarget (renderTarget); - renderTarget->Release (); - renderTarget = nullptr; - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::beginDraw () -{ - if (renderTarget) - { - auto scale = getScaleFactor (); - CGraphicsTransform transform; - transform.scale (scale, scale); - renderTarget->BeginDraw (); - renderTarget->SetTransform (convert (transform)); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::endDraw () -{ - if (renderTarget) - { - if (currentClip.isEmpty () == false) - { - getRenderTarget ()->PopAxisAlignedClip (); - currentClip = CRect (); - } - renderTarget->Flush (); - HRESULT result = renderTarget->EndDraw (); - if (result == (HRESULT)D2DERR_RECREATE_TARGET) - { - releaseRenderTarget (); - createRenderTarget (); - } - else - { - vstgui_assert (result == S_OK); - } - if (bitmap) - { - D2DBitmap* d2dBitmap = dynamic_cast (bitmap->getPlatformBitmap ().get ()); - D2DBitmapCache::removeBitmap (d2dBitmap); - } - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::init () -{ - COffscreenContext::init (); -} - -//----------------------------------------------------------------------------- -CGraphicsPath* D2DDrawContext::createGraphicsPath () -{ - return new CGraphicsPath (D2DGraphicsPathFactory::instance ()); -} - -//----------------------------------------------------------------------------- -CGraphicsPath* D2DDrawContext::createTextPath (const CFontRef font, UTF8StringPtr text) -{ - auto factory = D2DGraphicsPathFactory::instance (); - if (auto path = factory->createTextPath (font->getPlatformFont (), text)) - { - return new CGraphicsPath (D2DGraphicsPathFactory::instance (), std::move (path)); - } - return nullptr; -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawGraphicsPath (CGraphicsPath* graphicsPath, PathDrawMode mode, - CGraphicsTransform* t) -{ - if (renderTarget == nullptr || graphicsPath == nullptr) - return; - D2DApplyClip ac (this); - if (ac.isEmpty ()) - return; - - auto d2dPath = dynamic_cast ( - graphicsPath - ->getPlatformPath (mode == kPathFilledEvenOdd ? PlatformGraphicsPathFillMode::Alternate - : PlatformGraphicsPathFillMode::Winding) - .get ()); - if (d2dPath == nullptr) - return; - - ID2D1Geometry* path = nullptr; - if (t) - path = d2dPath->createTransformedGeometry (getD2DFactory (), *t); - else - { - path = d2dPath->getPathGeometry (); - path->AddRef (); - } - if (path) - { - if (mode == kPathFilled || mode == kPathFilledEvenOdd) - getRenderTarget ()->FillGeometry (path, getFillBrush ()); - else if (mode == kPathStroked) - getRenderTarget ()->DrawGeometry (path, getStrokeBrush (), (FLOAT)getLineWidth (), getStrokeStyle ()); - path->Release (); - } -} - -//----------------------------------------------------------------------------- -ID2D1GradientStopCollection* D2DDrawContext::createGradientStopCollection (const CGradient& gradient) const -{ - if (auto d2dGradient = dynamic_cast (gradient.getPlatformGradient ().get ())) - return d2dGradient->create (getRenderTarget (), getCurrentState ().globalAlpha); - return nullptr; -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::fillLinearGradient (CGraphicsPath* graphicsPath, const CGradient& gradient, - const CPoint& startPoint, const CPoint& endPoint, - bool evenOdd, CGraphicsTransform* t) -{ - if (renderTarget == nullptr || graphicsPath == nullptr) - return; - - D2DApplyClip ac (this, true); - if (ac.isEmpty ()) - return; - - auto d2dPath = dynamic_cast ( - graphicsPath - ->getPlatformPath (evenOdd ? PlatformGraphicsPathFillMode::Alternate - : PlatformGraphicsPathFillMode::Winding) - .get ()); - if (d2dPath == nullptr) - return; - ID2D1Geometry* path = nullptr; - if (t) - path = d2dPath->createTransformedGeometry (getD2DFactory (), *t); - else - { - path = d2dPath->getPathGeometry (); - path->AddRef (); - } - - if (path) - { - ID2D1GradientStopCollection* collection = createGradientStopCollection (gradient); - if (collection) - { - ID2D1LinearGradientBrush* brush = nullptr; - D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES properties; - properties.startPoint = makeD2DPoint (startPoint); - properties.endPoint = makeD2DPoint (endPoint); - if (SUCCEEDED (getRenderTarget ()->CreateLinearGradientBrush (properties, collection, &brush))) - { - getRenderTarget ()->FillGeometry (path, brush); - brush->Release (); - } - collection->Release (); - } - path->Release (); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::fillRadialGradient (CGraphicsPath* graphicsPath, const CGradient& gradient, - const CPoint& center, CCoord radius, - const CPoint& originOffset, bool evenOdd, - CGraphicsTransform* t) -{ - if (renderTarget == nullptr || graphicsPath == nullptr) - return; - - D2DApplyClip ac (this, true); - if (ac.isEmpty ()) - return; - - auto d2dPath = dynamic_cast ( - graphicsPath - ->getPlatformPath (evenOdd ? PlatformGraphicsPathFillMode::Alternate - : PlatformGraphicsPathFillMode::Winding) - .get ()); - if (d2dPath == nullptr) - return; - - ID2D1Geometry* path = nullptr; - if (t) - path = d2dPath->createTransformedGeometry (getD2DFactory (), *t); - else - { - path = d2dPath->getPathGeometry (); - path->AddRef (); - } - - if (path) - { - if (ID2D1GradientStopCollection* collection = createGradientStopCollection (gradient)) - { - // brush properties - ID2D1RadialGradientBrush* brush = nullptr; - D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES properties; - properties.center = makeD2DPoint (center); - properties.gradientOriginOffset = makeD2DPoint (originOffset); - properties.radiusX = (FLOAT)radius; - properties.radiusY = (FLOAT)radius; - - if (SUCCEEDED (getRenderTarget ()->CreateRadialGradientBrush (properties, - collection, &brush))) - { - getRenderTarget ()->FillGeometry (path, brush); - brush->Release (); - } - collection->Release (); - } - path->Release (); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::clearRect (const CRect& rect) -{ - if (renderTarget) - { - CRect oldClip = getCurrentState ().clipRect; - setClipRect (rect); - D2DApplyClip ac (this); - renderTarget->Clear (D2D1::ColorF (1.f, 1.f, 1.f, 0.f)); - setClipRect (oldClip); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset, float alpha) -{ - if (renderTarget == nullptr) - return; - ConcatClip concatClip (*this, dest); - D2DApplyClip ac (this); - if (ac.isEmpty ()) - return; - - double transformedScaleFactor = getScaleFactor (); - CGraphicsTransform t = getCurrentTransform (); - if (t.m11 == t.m22 && t.m12 == 0 && t.m21 == 0) - transformedScaleFactor *= t.m11; - IPlatformBitmap* platformBitmap = bitmap->getBestPlatformBitmapForScaleFactor (transformedScaleFactor); - D2DBitmap* d2dBitmap = platformBitmap ? dynamic_cast (platformBitmap) : nullptr; - if (d2dBitmap && d2dBitmap->getSource ()) - { - if (auto d2d1Bitmap = D2DBitmapCache::getBitmap (d2dBitmap, renderTarget, device)) - { - double bitmapScaleFactor = platformBitmap->getScaleFactor (); - CGraphicsTransform bitmapTransform; - bitmapTransform.scale (1./bitmapScaleFactor, 1./bitmapScaleFactor); - Transform transform (*this, bitmapTransform); - - CRect d (dest); - d.setWidth (bitmap->getWidth ()); - d.setHeight (bitmap->getHeight ()); - d.offset (-offset.x, -offset.y); - d.makeIntegral (); - CRect source; - source.setWidth (d2d1Bitmap->GetSize ().width); - source.setHeight (d2d1Bitmap->GetSize ().height); - - D2D1_BITMAP_INTERPOLATION_MODE mode; - switch (getCurrentState ().bitmapQuality) - { - case BitmapInterpolationQuality::kLow: - mode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; - break; - - case BitmapInterpolationQuality::kMedium: - case BitmapInterpolationQuality::kHigh: - default: - mode = D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; - break; - } - - D2D1_RECT_F sourceRect = makeD2DRect (source); - renderTarget->DrawBitmap (d2d1Bitmap, makeD2DRect (d), alpha * getCurrentState ().globalAlpha, mode, &sourceRect); - } - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawLineInternal (CPoint start, CPoint end) -{ - renderTarget->DrawLine (makeD2DPoint (start), makeD2DPoint (end), strokeBrush, (FLOAT)getCurrentState ().frameWidth, strokeStyle); -} - -//----------------------------------------------------------------------------- -bool D2DDrawContext::needsHalfPointOffset () const -{ - return static_cast (getCurrentState ().frameWidth) % 2 != 0; -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawLine (const LinePair& line) -{ - if (renderTarget == nullptr) - return; - D2DApplyClip ac (this); - if (ac.isEmpty ()) - return; - - CPoint start (line.first); - CPoint end (line.second); - if (getDrawMode ().integralMode ()) - { - pixelAllign (start); - pixelAllign (end); - } - if (needsHalfPointOffset ()) - { - start.offset (0.5, 0.5); - end.offset (0.5, 0.5); - } - drawLineInternal (start, end); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawLines (const LineList& lines) -{ - if (lines.empty () || renderTarget == nullptr) - return; - D2DApplyClip ac (this); - if (ac.isEmpty ()) - return; - - bool needsOffset = needsHalfPointOffset (); - bool integralMode = getDrawMode ().integralMode (); - for (const auto& line : lines) - { - CPoint start (line.first); - CPoint end (line.second); - if (integralMode) - { - pixelAllign (start); - pixelAllign (end); - } - if (needsOffset) - { - start.offset (0.5, 0.5); - end.offset (0.5, 0.5); - } - drawLineInternal (start, end); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle) -{ - if (renderTarget == nullptr || polygonPointList.empty ()) - return; - D2DApplyClip ac (this); - if (ac.isEmpty ()) - return; - - auto path = owned (createGraphicsPath ()); - path->beginSubpath (polygonPointList[0]); - for (uint32_t i = 1; i < polygonPointList.size (); ++i) - { - path->addLine (polygonPointList[i]); - } - if (drawStyle == kDrawFilled || drawStyle == kDrawFilledAndStroked) - { - drawGraphicsPath (path, kPathFilled); - } - if (drawStyle == kDrawStroked || drawStyle == kDrawFilledAndStroked) - { - drawGraphicsPath (path, kPathStroked); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawRect (const CRect &_rect, const CDrawStyle drawStyle) -{ - if (renderTarget == nullptr) - return; - D2DApplyClip ac (this); - if (ac.isEmpty ()) - return; - CRect rect (_rect); - if (drawStyle != kDrawFilled) - { - rect.right -= 1.; - rect.bottom -= 1.; - } - if (drawStyle == kDrawFilled || drawStyle == kDrawFilledAndStroked) - { - renderTarget->FillRectangle (makeD2DRect (rect), fillBrush); - } - if (drawStyle == kDrawStroked || drawStyle == kDrawFilledAndStroked) - { - if (needsHalfPointOffset ()) - { - rect.offset (0.5, 0.5); - } - renderTarget->DrawRectangle (makeD2DRect (rect), strokeBrush, (FLOAT)getCurrentState ().frameWidth, strokeStyle); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawArc (const CRect& _rect, const float _startAngle, const float _endAngle, const CDrawStyle drawStyle) -{ - if (auto path = owned (createGraphicsPath ())) - { - CRect rect (_rect); - if (getDrawMode ().integralMode ()) - pixelAllign (rect); - path->addArc (rect, _startAngle, _endAngle, true); - if (drawStyle == kDrawFilled || drawStyle == kDrawFilledAndStroked) - drawGraphicsPath (path, kPathFilled); - if (drawStyle == kDrawStroked || drawStyle == kDrawFilledAndStroked) - drawGraphicsPath (path, kPathStroked); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawEllipse (const CRect &_rect, const CDrawStyle drawStyle) -{ - if (renderTarget == nullptr) - return; - D2DApplyClip ac (this); - if (ac.isEmpty ()) - return; - CRect rect (_rect); - if (getDrawMode ().integralMode ()) - pixelAllign (rect); - if (drawStyle == kDrawStroked) - rect.inset (0.5, 0.5); - CPoint center (rect.getTopLeft ()); - center.offset (rect.getWidth () / 2., rect.getHeight () / 2.); - D2D1_ELLIPSE ellipse; - ellipse.point = makeD2DPoint (center); - ellipse.radiusX = (FLOAT)(rect.getWidth () / 2.); - ellipse.radiusY = (FLOAT)(rect.getHeight () / 2.); - if (drawStyle == kDrawFilled || drawStyle == kDrawFilledAndStroked) - { - renderTarget->FillEllipse (ellipse, fillBrush); - } - if (drawStyle == kDrawStroked || drawStyle == kDrawFilledAndStroked) - { - renderTarget->DrawEllipse (ellipse, strokeBrush, (FLOAT)getCurrentState ().frameWidth, strokeStyle); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::drawPoint (const CPoint &point, const CColor& color) -{ - saveGlobalState (); - setLineWidth (1); - setFrameColor (color); - CPoint point2 (point); - point2.x++; - COffscreenContext::drawLine (point, point2); - restoreGlobalState (); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setLineStyle (const CLineStyle& style) -{ - if (strokeStyle && getCurrentState ().lineStyle == style) - return; - setLineStyleInternal (style); - COffscreenContext::setLineStyle (style); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setLineStyleInternal (const CLineStyle& style) -{ - if (strokeStyle) - { - strokeStyle->Release (); - strokeStyle = nullptr; - } - D2D1_STROKE_STYLE_PROPERTIES properties; - switch (style.getLineCap ()) - { - case CLineStyle::kLineCapButt: properties.startCap = properties.endCap = properties.dashCap = D2D1_CAP_STYLE_FLAT; break; - case CLineStyle::kLineCapRound: properties.startCap = properties.endCap = properties.dashCap = D2D1_CAP_STYLE_ROUND; break; - case CLineStyle::kLineCapSquare: properties.startCap = properties.endCap = properties.dashCap = D2D1_CAP_STYLE_SQUARE; break; - } - switch (style.getLineJoin ()) - { - case CLineStyle::kLineJoinMiter: properties.lineJoin = D2D1_LINE_JOIN_MITER; break; - case CLineStyle::kLineJoinRound: properties.lineJoin = D2D1_LINE_JOIN_ROUND; break; - case CLineStyle::kLineJoinBevel: properties.lineJoin = D2D1_LINE_JOIN_BEVEL; break; - } - properties.dashOffset = (FLOAT)style.getDashPhase (); - properties.miterLimit = 10.f; - if (style.getDashCount ()) - { - properties.dashStyle = D2D1_DASH_STYLE_CUSTOM; - FLOAT* lengths = new FLOAT[style.getDashCount ()]; - for (uint32_t i = 0; i < style.getDashCount (); i++) - lengths[i] = (FLOAT)style.getDashLengths ()[i]; - getD2DFactory ()->CreateStrokeStyle (properties, lengths, style.getDashCount (), &strokeStyle); - delete [] lengths; - } - else - { - properties.dashStyle = D2D1_DASH_STYLE_SOLID; - getD2DFactory ()->CreateStrokeStyle (properties, nullptr, 0, &strokeStyle); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setLineWidth (CCoord width) -{ - if (getCurrentState ().frameWidth == width) - return; - COffscreenContext::setLineWidth (width); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setDrawMode (CDrawMode mode) -{ - if (getCurrentState ().drawMode == mode && getCurrentState ().drawMode.integralMode () == mode.integralMode ()) - return; - setDrawModeInternal (mode); - COffscreenContext::setDrawMode (mode); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setDrawModeInternal (CDrawMode mode) -{ - if (renderTarget) - { - if (mode == kAntiAliasing) - renderTarget->SetAntialiasMode (D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); - else - renderTarget->SetAntialiasMode (D2D1_ANTIALIAS_MODE_ALIASED); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setClipRect (const CRect &clip) -{ - COffscreenContext::setClipRect (clip); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::resetClipRect () -{ - COffscreenContext::resetClipRect (); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setFillColor (const CColor& color) -{ - if (getCurrentState ().fillColor == color) - return; - setFillColorInternal (color); - COffscreenContext::setFillColor (color); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setFrameColor (const CColor& color) -{ - if (getCurrentState ().frameColor == color) - return; - setFrameColorInternal (color); - COffscreenContext::setFrameColor (color); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setFontColor (const CColor& color) -{ - if (getCurrentState ().fontColor == color) - return; - setFontColorInternal (color); - COffscreenContext::setFontColor (color); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setFillColorInternal (const CColor& color) -{ - if (fillBrush) - { - fillBrush->Release (); - fillBrush = nullptr; - } - if (renderTarget) - { - renderTarget->CreateSolidColorBrush (toColorF (color, getCurrentState ().globalAlpha), - &fillBrush); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setFrameColorInternal (const CColor& color) -{ - if (strokeBrush) - { - strokeBrush->Release (); - strokeBrush = nullptr; - } - if (renderTarget) - { - renderTarget->CreateSolidColorBrush (toColorF (color, getCurrentState ().globalAlpha), - &strokeBrush); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setFontColorInternal (const CColor& color) -{ - if (fontBrush) - { - fontBrush->Release (); - fontBrush = nullptr; - } - if (renderTarget) - { - renderTarget->CreateSolidColorBrush (toColorF (color, getCurrentState ().globalAlpha), - &fontBrush); - } -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::setGlobalAlpha (float newAlpha) -{ - if (getCurrentState ().globalAlpha == newAlpha) - return; - COffscreenContext::setGlobalAlpha (newAlpha); - setFrameColorInternal (getCurrentState ().frameColor); - setFillColorInternal (getCurrentState ().fillColor); - setFontColorInternal (getCurrentState ().fontColor); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::saveGlobalState () -{ - COffscreenContext::saveGlobalState (); -} - -//----------------------------------------------------------------------------- -void D2DDrawContext::restoreGlobalState () -{ - CColor prevFillColor = getCurrentState ().fillColor; - CColor prevFrameColor = getCurrentState ().frameColor; - CColor prevFontColor = getCurrentState ().fontColor; - CLineStyle prevLineStye = getCurrentState ().lineStyle; - CDrawMode prevDrawMode = getCurrentState ().drawMode; - float prevAlpha = getCurrentState ().globalAlpha; - COffscreenContext::restoreGlobalState (); - if (prevAlpha != getCurrentState ().globalAlpha) - { - float _prevAlpha = getCurrentState ().globalAlpha; - getCurrentState ().globalAlpha = -1.f; - setGlobalAlpha (_prevAlpha); - } - else - { - if (prevFillColor != getCurrentState ().fillColor) - { - setFillColorInternal (getCurrentState ().fillColor); - } - if (prevFrameColor != getCurrentState ().frameColor) - { - setFrameColorInternal (getCurrentState ().frameColor); - } - if (prevFontColor != getCurrentState ().fontColor) - { - setFontColorInternal (getCurrentState ().fontColor); - } - } - if (prevLineStye != getCurrentState ().lineStyle) - { - setLineStyleInternal (getCurrentState ().lineStyle); - } - if (prevDrawMode != getCurrentState ().drawMode) - { - setDrawModeInternal (getCurrentState ().drawMode); - } -} - -} // VSTGUI - -#endif // WINDOWS diff --git a/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.h b/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.h deleted file mode 100644 index acbd5b0b4..000000000 --- a/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.h +++ /dev/null @@ -1,166 +0,0 @@ -// 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 "../../../coffscreencontext.h" - -#if WINDOWS -struct IUnknown; -struct ID2D1DeviceContext; - -#include "d2dbitmap.h" -#include -#include -#include - -namespace VSTGUI { -class CGradient; - -//----------------------------------------------------------------------------- -class D2DDrawContext final : public COffscreenContext -{ -public: - D2DDrawContext (HWND window, const CRect& drawSurface); - D2DDrawContext (ID2D1DeviceContext* deviceContext, const CRect& drawSurface, - ID2D1Device* device = nullptr); - D2DDrawContext (D2DBitmap* bitmap); - ~D2DDrawContext (); - - bool usable () const { return getRenderTarget () != nullptr; } - - ID2D1RenderTarget* getRenderTarget () const { return renderTarget; } - ID2D1SolidColorBrush* getFillBrush () const { return fillBrush; } - ID2D1SolidColorBrush* getStrokeBrush () const { return strokeBrush; } - ID2D1SolidColorBrush* getFontBrush () const { return fontBrush; } - ID2D1StrokeStyle* getStrokeStyle () const { return strokeStyle; } - - // CDrawContext - void drawLine (const LinePair& line) override; - void drawLines (const LineList& lines) override; - void drawPolygon (const PointList& polygonPointList, const CDrawStyle drawStyle = kDrawStroked) override; - void drawRect (const CRect &rect, const CDrawStyle drawStyle = kDrawStroked) override; - void drawArc (const CRect &rect, const float startAngle1, const float endAngle2, const CDrawStyle drawStyle = kDrawStroked) override; - void drawEllipse (const CRect &rect, const CDrawStyle drawStyle = kDrawStroked) override; - void drawPoint (const CPoint &point, const CColor& color) override; - void drawBitmap (CBitmap* bitmap, const CRect& dest, const CPoint& offset = CPoint (0, 0), float alpha = 1.f) override; - void clearRect (const CRect& rect) override; - void setLineStyle (const CLineStyle& style) override; - void setLineWidth (CCoord width) override; - void setDrawMode (CDrawMode mode) override; - void setClipRect (const CRect &clip) override; - void resetClipRect () override; - void setFillColor (const CColor& color) override; - void setFrameColor (const CColor& color) override; - void setFontColor (const CColor& color) override; - void setGlobalAlpha (float newAlpha) override; - void saveGlobalState () override; - void restoreGlobalState () override; - CGraphicsPath* createGraphicsPath () override; - CGraphicsPath* createTextPath (const CFontRef font, UTF8StringPtr text) override; - void drawGraphicsPath (CGraphicsPath* path, PathDrawMode mode = kPathFilled, CGraphicsTransform* transformation = nullptr) override; - void fillLinearGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& startPoint, const CPoint& endPoint, bool evenOdd = false, CGraphicsTransform* transformation = nullptr) override; - void fillRadialGradient (CGraphicsPath* path, const CGradient& gradient, const CPoint& center, CCoord radius, const CPoint& originOffset = CPoint (0, 0), bool evenOdd = false, CGraphicsTransform* transformation = nullptr) override; - - void beginDraw () override; - void endDraw () override; - - double getScaleFactor () const override { return scaleFactor; } - - //----------------------------------------------------------------------------- - class D2DApplyClip - { - public: - D2DApplyClip (D2DDrawContext* drawContext, bool halfPointOffset = false); - ~D2DApplyClip (); - bool isEmpty () const { return applyClip.isEmpty (); } - protected: - D2DDrawContext* drawContext; - CRect applyClip; - bool layerIsUsed {false}; - }; - - template void pixelAllign (T& rect) const; - -//----------------------------------------------------------------------------- -protected: - void init () override; - void createRenderTarget (); - void releaseRenderTarget (); - ID2D1GradientStopCollection* createGradientStopCollection (const CGradient& gradient) const; - - void setFillColorInternal (const CColor& color); - void setFrameColorInternal (const CColor& color); - void setFontColorInternal (const CColor& color); - void setLineStyleInternal (const CLineStyle& style); - void setDrawModeInternal (CDrawMode mode); - void drawLineInternal (CPoint start, CPoint end); - - bool needsHalfPointOffset () const; - - HWND window; - ID2D1Device* device {nullptr}; - ID2D1RenderTarget* renderTarget; - ID2D1SolidColorBrush* fillBrush; - ID2D1SolidColorBrush* strokeBrush; - ID2D1SolidColorBrush* fontBrush; - ID2D1StrokeStyle* strokeStyle; - CRect currentClip; - double scaleFactor {1.}; -}; - -//----------------------------------------------------------------------------- -template void D2DDrawContext::pixelAllign (T& obj) const -{ - const CGraphicsTransform& t = getCurrentTransform (); - CGraphicsTransform tInv = t.inverse (); - t.transform (obj); - obj.makeIntegral (); - tInv.transform (obj); -} - -//----------------------------------------------------------------------------- -static inline D2D1_RECT_F makeD2DRect (const CRect& r) -{ - D2D1_RECT_F dr = {(FLOAT)r.left, (FLOAT)r.top, (FLOAT)r.right, (FLOAT)r.bottom}; - return dr; -} - -//----------------------------------------------------------------------------- -static inline D2D1_POINT_2F makeD2DPoint (const CPoint& p) -{ - D2D1_POINT_2F dp = {(FLOAT)p.x, (FLOAT)p.y}; - return dp; -} - -static inline D2D1_SIZE_F makeD2DSize (CCoord width, CCoord height) -{ - D2D1_SIZE_F ds = {(FLOAT)width, (FLOAT)height}; - return ds; -} - -//----------------------------------------------------------------------------- -static inline D2D1_MATRIX_3X2_F convert (const CGraphicsTransform& t) -{ - D2D1_MATRIX_3X2_F matrix; - matrix._11 = static_cast (t.m11); - matrix._12 = static_cast (t.m21); - matrix._21 = static_cast (t.m12); - matrix._22 = static_cast (t.m22); - matrix._31 = static_cast (t.dx); - matrix._32 = static_cast (t.dy); - return matrix; -} - -//----------------------------------------------------------------------------- -static inline D2D1::ColorF toColorF (CColor c, float alpha) -{ - return D2D1::ColorF (c.normRed (), c.normGreen (), c.normBlue (), - c.normAlpha () * alpha); -} - - -} // VSTGUI - -#endif // WINDOWS diff --git a/vstgui/lib/platform/win32/direct2d/d2dfont.cpp b/vstgui/lib/platform/win32/direct2d/d2dfont.cpp index cd815ae5d..6454512ba 100644 --- a/vstgui/lib/platform/win32/direct2d/d2dfont.cpp +++ b/vstgui/lib/platform/win32/direct2d/d2dfont.cpp @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -11,7 +11,8 @@ #include "../win32factory.h" #include "../winstring.h" #include "../comptr.h" -#include "d2ddrawcontext.h" +#include "../../../ccolor.h" +#include "d2dgraphicscontext.h" #include #include #include @@ -24,8 +25,8 @@ #define VSTGUI_WIN32_CUSTOMFONT_SUPPORT 1 #else #define VSTGUI_WIN32_CUSTOMFONT_SUPPORT 0 -#pragma message( \ - "Warning: VSTGUI Custom Font support is only available when building with the Windows 10 Creator Update SDK or newer") +#pragma message( \ + "Warning: VSTGUI Custom Font support is only available when building with the Windows 10 Creator Update SDK or newer") #endif namespace VSTGUI { @@ -71,7 +72,7 @@ struct CustomFonts { COM::Ptr fontFile; if (FAILED (factory5->CreateFontFileReference (file.data (), nullptr, - fontFile.adoptPtr ()))) + fontFile.adoptPtr ()))) continue; fontSetBuilder->AddFontFile (fontFile.get ()); } @@ -120,9 +121,7 @@ static std::once_flag customFontsOnceFlag; //----------------------------------------------------------------------------- static CustomFonts* getCustomFonts () { - std::call_once (customFontsOnceFlag, [] () { - customFonts = std::make_unique (); - }); + std::call_once (customFontsOnceFlag, [] () { customFonts = std::make_unique (); }); return customFonts.get (); } @@ -154,26 +153,29 @@ static void gatherFonts (const FontFamilyCallback& callback, IDWriteFontCollecti //----------------------------------------------------------------------------- static COM::Ptr getFont (IDWriteTextFormat* format, int32_t style) { - DWRITE_FONT_STYLE fontStyle = (style & kItalicFace) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; - DWRITE_FONT_WEIGHT fontWeight = (style & kBoldFace) ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL; + DWRITE_FONT_STYLE fontStyle = + (style & kItalicFace) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; + DWRITE_FONT_WEIGHT fontWeight = + (style & kBoldFace) ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL; IDWriteFontCollection* fontCollection = nullptr; format->GetFontCollection (&fontCollection); if (!fontCollection) return {}; auto nameLength = format->GetFontFamilyNameLength () + 1; - auto familyName = std::unique_ptr (new WCHAR [nameLength]); + auto familyName = std::unique_ptr (new WCHAR[nameLength]); if (FAILED (format->GetFontFamilyName (familyName.get (), nameLength))) return {}; UINT32 index = 0; BOOL exists = FALSE; - if (FAILED (fontCollection->FindFamilyName (familyName.get (), &index, &exists))) + if (FAILED (fontCollection->FindFamilyName (familyName.get (), &index, &exists)) || !exists) return {}; COM::Ptr fontFamily; fontCollection->GetFontFamily (index, fontFamily.adoptPtr ()); if (fontFamily) { COM::Ptr font; - fontFamily->GetFirstMatchingFont (fontWeight, DWRITE_FONT_STRETCH_NORMAL, fontStyle, font.adoptPtr ()); + fontFamily->GetFirstMatchingFont (fontWeight, DWRITE_FONT_STRETCH_NORMAL, fontStyle, + font.adoptPtr ()); return font; } return {}; @@ -197,22 +199,16 @@ bool D2DFont::getAllFontFamilies (const FontFamilyCallback& callback) } //----------------------------------------------------------------------------- -void D2DFont::terminate () -{ - D2DFontPrivate::customFonts = nullptr; -} +void D2DFont::terminate () { D2DFontPrivate::customFonts = nullptr; } //----------------------------------------------------------------------------- D2DFont::D2DFont (const UTF8String& name, const CCoord& size, const int32_t& style) -: textFormat (nullptr) -, ascent (-1) -, descent (-1) -, leading (-1) -, capHeight (-1) -, style (style) +: textFormat (nullptr), ascent (-1), descent (-1), leading (-1), capHeight (-1), style (style) { - DWRITE_FONT_STYLE fontStyle = (style & kItalicFace) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; - DWRITE_FONT_WEIGHT fontWeight = (style & kBoldFace) ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL; + DWRITE_FONT_STYLE fontStyle = + (style & kItalicFace) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; + DWRITE_FONT_WEIGHT fontWeight = + (style & kBoldFace) ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL; UTF8StringHelper nameStr (name.data ()); IDWriteFontCollection* fontCollection = nullptr; @@ -222,8 +218,8 @@ D2DFont::D2DFont (const UTF8String& name, const CCoord& size, const int32_t& sty fontCollection = customFonts->getFontCollection (); getDWriteFactory ()->CreateTextFormat (nameStr, fontCollection, fontWeight, fontStyle, - DWRITE_FONT_STRETCH_NORMAL, (FLOAT)size, L"en-us", - &textFormat); + DWRITE_FONT_STRETCH_NORMAL, (FLOAT)size, L"en-us", + &textFormat); if (textFormat) { if (auto font = D2DFontPrivate::getFont (textFormat, style)) @@ -271,59 +267,50 @@ IDWriteTextLayout* D2DFont::createTextLayout (IPlatformString* string) const const auto* winString = dynamic_cast (string); IDWriteTextLayout* textLayout = nullptr; if (winString) - getDWriteFactory ()->CreateTextLayout (winString->getWideString (), (UINT32)wcslen (winString->getWideString ()), textFormat, 10000, 1000, &textLayout); + getDWriteFactory ()->CreateTextLayout (winString->getWideString (), + (UINT32)wcslen (winString->getWideString ()), + textFormat, 10000, 1000, &textLayout); return textLayout; } //----------------------------------------------------------------------------- -void D2DFont::drawString (CDrawContext* context, IPlatformString* string, const CPoint& p, bool antialias) const +void D2DFont::drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, + const CPoint& p, const CColor& color, bool antialias) const { - auto* d2dContext = dynamic_cast (context); - if (d2dContext && textFormat) + if (!textFormat || !context || !string) + return; + + if (auto graphicsContext = std::dynamic_pointer_cast (context)) { - D2DDrawContext::D2DApplyClip ac (d2dContext); - if (ac.isEmpty ()) + IDWriteTextLayout* textLayout = createTextLayout (string); + if (!textLayout) return; - ID2D1RenderTarget* renderTarget = d2dContext->getRenderTarget (); - if (renderTarget) + if (style & kUnderlineFace) { - IDWriteTextLayout* textLayout = createTextLayout (string); - if (textLayout) - { - if (style & kUnderlineFace) - { - DWRITE_TEXT_RANGE range = { 0, UINT_MAX }; - textLayout->SetUnderline (true, range); - } - if (style & kStrikethroughFace) - { - DWRITE_TEXT_RANGE range = { 0, UINT_MAX }; - textLayout->SetStrikethrough (true, range); - } - renderTarget->SetTextAntialiasMode (antialias ? D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE : D2D1_TEXT_ANTIALIAS_MODE_ALIASED); - - CPoint pos (p); - DWRITE_LINE_METRICS lm; - UINT lineCount; - if (SUCCEEDED (textLayout->GetLineMetrics (&lm, 1, &lineCount))) - pos.y -= lm.baseline; - else - pos.y -= textFormat->GetFontSize (); - - if (context->getDrawMode ().integralMode ()) - pos.makeIntegral (); - pos.y += 0.5; - - D2D1_POINT_2F origin = {(FLOAT)(p.x), (FLOAT)(pos.y)}; - d2dContext->getRenderTarget ()->DrawTextLayout (origin, textLayout, d2dContext->getFontBrush ()); - textLayout->Release (); - } + DWRITE_TEXT_RANGE range = {0, UINT_MAX}; + textLayout->SetUnderline (true, range); } + if (style & kStrikethroughFace) + { + DWRITE_TEXT_RANGE range = {0, UINT_MAX}; + textLayout->SetStrikethrough (true, range); + } + CPoint pos (p); + DWRITE_LINE_METRICS lm; + UINT lineCount; + if (SUCCEEDED (textLayout->GetLineMetrics (&lm, 1, &lineCount))) + pos.y -= lm.baseline; + else + pos.y -= textFormat->GetFontSize (); + + graphicsContext->drawTextLayout (textLayout, pos, color, antialias); + textLayout->Release (); } } //----------------------------------------------------------------------------- -CCoord D2DFont::getStringWidth (CDrawContext* context, IPlatformString* string, bool antialias) const +CCoord D2DFont::getStringWidth (const PlatformGraphicsDeviceContextPtr&, IPlatformString* string, + bool antialias) const { CCoord result = 0; if (textFormat) diff --git a/vstgui/lib/platform/win32/direct2d/d2dfont.h b/vstgui/lib/platform/win32/direct2d/d2dfont.h index 7b641efbd..044e7f8fb 100644 --- a/vstgui/lib/platform/win32/direct2d/d2dfont.h +++ b/vstgui/lib/platform/win32/direct2d/d2dfont.h @@ -40,8 +40,10 @@ class D2DFont final : public IPlatformFont, public IFontPainter const IFontPainter* getPainter () const override { return this; } - void drawString (CDrawContext* context, IPlatformString* string, const CPoint& p, bool antialias = true) const override; - CCoord getStringWidth (CDrawContext* context, IPlatformString* string, bool antialias = true) const override; + void drawString (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, + const CPoint& p, const CColor& color, bool antialias = true) const override; + CCoord getStringWidth (const PlatformGraphicsDeviceContextPtr& context, IPlatformString* string, + bool antialias = true) const override; IDWriteTextFormat* textFormat; double ascent; diff --git a/vstgui/lib/platform/win32/direct2d/d2dgradient.cpp b/vstgui/lib/platform/win32/direct2d/d2dgradient.cpp index 8a21afad7..ed45b1805 100644 --- a/vstgui/lib/platform/win32/direct2d/d2dgradient.cpp +++ b/vstgui/lib/platform/win32/direct2d/d2dgradient.cpp @@ -6,7 +6,7 @@ #if WINDOWS -#include "d2ddrawcontext.h" +#include "d2d.h" #include namespace VSTGUI { @@ -21,7 +21,7 @@ ID2D1GradientStopCollection* D2DGradient::create (ID2D1RenderTarget* renderTarge for (auto it = getColorStops ().begin (); it != getColorStops ().end (); ++it, ++index) { gradientStops[index].position = static_cast (it->first); - gradientStops[index].color = toColorF (it->second, globalAlpha); + gradientStops[index].color = convert (it->second, globalAlpha); } ID2D1GradientStopCollection* collection = nullptr; auto hr = renderTarget->CreateGradientStopCollection ( diff --git a/vstgui/lib/platform/win32/direct2d/d2dgraphicscontext.cpp b/vstgui/lib/platform/win32/direct2d/d2dgraphicscontext.cpp new file mode 100644 index 000000000..d16758672 --- /dev/null +++ b/vstgui/lib/platform/win32/direct2d/d2dgraphicscontext.cpp @@ -0,0 +1,949 @@ +// 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 "d2dgraphicscontext.h" +#include "d2dgradient.h" +#include "d2dgraphicspath.h" +#include "d2dbitmap.h" +#include "d2dbitmapcache.h" +#include "d2dgradient.h" +#include "d2d.h" +#include "../comptr.h" +#include "../../../crect.h" +#include "../../../cgraphicstransform.h" +#include "../../../ccolor.h" +#include "../../../cdrawdefs.h" +#include "../../../clinestyle.h" +#include + +#include +#include + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace { + +//------------------------------------------------------------------------ +struct TransformGuard +{ + TransformGuard (ID2D1DeviceContext* context) : context (context) + { + context->GetTransform (&matrix); + } + ~TransformGuard () noexcept { context->SetTransform (matrix); } + + D2D1_MATRIX_3X2_F matrix; + ID2D1DeviceContext* context; +}; + +//------------------------------------------------------------------------ +} // anonymous namespace + +//------------------------------------------------------------------------ +struct D2DGraphicsDeviceFactory::Impl +{ + std::vector> devices; +}; + +//------------------------------------------------------------------------ +D2DGraphicsDeviceFactory::D2DGraphicsDeviceFactory () { impl = std::make_unique (); } + +//------------------------------------------------------------------------ +D2DGraphicsDeviceFactory::~D2DGraphicsDeviceFactory () noexcept {} + +//------------------------------------------------------------------------ +PlatformGraphicsDevicePtr + D2DGraphicsDeviceFactory::getDeviceForScreen (ScreenInfo::Identifier screen) const +{ + if (impl->devices.empty ()) + return nullptr; + return impl->devices.front (); +} + +//------------------------------------------------------------------------ +PlatformGraphicsDevicePtr D2DGraphicsDeviceFactory::find (ID2D1Device* dev) const +{ + auto it = std::find_if (impl->devices.begin (), impl->devices.end (), + [dev] (const auto& el) { return el->get () == dev; }); + if (it != impl->devices.end ()) + return *it; + return nullptr; +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceFactory::addDevice (const std::shared_ptr& device) const +{ + impl->devices.push_back (device); +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceFactory::removeDevice (const std::shared_ptr& device) const +{ + auto it = std::find (impl->devices.begin (), impl->devices.end (), device); + if (it != impl->devices.end ()) + impl->devices.erase (it); +} + +//------------------------------------------------------------------------ +struct D2DGraphicsDevice::Impl +{ + COM::Ptr device; +}; + +//------------------------------------------------------------------------ +D2DGraphicsDevice::D2DGraphicsDevice (ID2D1Device* device) +{ + impl = std::make_unique (); + impl->device = COM::share (device); +} + +//------------------------------------------------------------------------ +D2DGraphicsDevice::~D2DGraphicsDevice () noexcept {} + +//------------------------------------------------------------------------ +PlatformGraphicsDeviceContextPtr + D2DGraphicsDevice::createBitmapContext (const PlatformBitmapPtr& bitmap) const +{ + auto d2dBitmap = bitmap.cast (); + if (d2dBitmap) + { + + COM::Ptr deviceContext; + auto hr = + impl->device->CreateDeviceContext (D2D1_DEVICE_CONTEXT_OPTIONS_NONE, deviceContext.adoptPtr ()); + if (FAILED (hr)) + return nullptr; + + D2DBitmapCache::removeBitmap (d2dBitmap); + + D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1 ( + D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, + D2D1::PixelFormat (DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + COM::Ptr bitmapTarget; + deviceContext->CreateBitmapFromWicBitmap (d2dBitmap->getSource (), &props, bitmapTarget.adoptPtr ()); + if (!bitmapTarget) + return nullptr; + + deviceContext->SetTarget (bitmapTarget.get ()); + + TransformMatrix tm; + tm.scale (d2dBitmap->getScaleFactor (), d2dBitmap->getScaleFactor ()); + deviceContext->SetTransform (convert (tm)); + + return std::make_shared (*this, deviceContext.get (), + TransformMatrix {}); + } + return nullptr; +} + +//------------------------------------------------------------------------ +ID2D1Device* D2DGraphicsDevice::get () const { return impl->device.get (); } + +//------------------------------------------------------------------------ +struct D2DGraphicsDeviceContext::Impl +{ + Impl (const D2DGraphicsDevice& device, ID2D1DeviceContext* deviceContext, + const TransformMatrix& tm) + : device (device), deviceContext (COM::share (deviceContext)), globalTM (tm) + { +#if DEBUG + COM::Ptr d2dDevice; + deviceContext->GetDevice (d2dDevice.adoptPtr ()); + vstgui_assert (d2dDevice.get () == device.get ()); +#endif + } + + template + void doInContext (Proc p, CPoint transformOffset = {}) + { + if (state.clip.isEmpty ()) + return; + + TransformGuard tmGuard (deviceContext.get ()); + + auto transform = convert (tmGuard.matrix) * globalTM * state.tm; + transform.scale (scaleFactor, scaleFactor); + transform.translate (transformOffset); + bool useLayer = transform.m12 != 0. || transform.m21 != 0.; + if (useLayer) + { // we have a rotated matrix, we need to use a layer + COM::Ptr factory {}; + deviceContext->GetFactory (factory.adoptPtr ()); + COM::Ptr geometry; + if (SUCCEEDED ( + factory->CreateRectangleGeometry (convert (state.clip), geometry.adoptPtr ()))) + { + if (applyClip.isEmpty () == false) + deviceContext->PopAxisAlignedClip (); + deviceContext->PushLayer (D2D1::LayerParameters (D2D1::InfiniteRect (), + geometry.get (), + D2D1_ANTIALIAS_MODE_ALIASED), + nullptr); + geometry->Release (); + applyClip = state.clip; + } + else + { + useLayer = false; + } + } + if (!useLayer) + { + auto newClip = state.clip; + globalTM.transform (newClip); + if (applyClip != newClip) + { + if (applyClip.isEmpty () == false) + deviceContext->PopAxisAlignedClip (); + if (newClip.isEmpty () == false) + deviceContext->PushAxisAlignedClip (convert (newClip), + D2D1_ANTIALIAS_MODE_ALIASED); + applyClip = newClip; + } + } + deviceContext->SetTransform (convert (transform)); + + p (deviceContext.get ()); + + if (useLayer) + deviceContext->PopLayer (); + } + + //----------------------------------------------------------------------------- + void applyFrameColor () + { + if (state.frameBrush) + return; + deviceContext->CreateSolidColorBrush (convert (state.frameColor, state.globalAlpha), + state.frameBrush.adoptPtr ()); + } + + //----------------------------------------------------------------------------- + void applyFillColor () + { + if (state.fillBrush) + return; + deviceContext->CreateSolidColorBrush (convert (state.fillColor, state.globalAlpha), + state.fillBrush.adoptPtr ()); + } + + //----------------------------------------------------------------------------- + void applyFontColor (CColor color) + { + if (state.fontColor != color) + state.fontBrush.reset (); + if (state.fontBrush) + return; + state.fontColor = color; + deviceContext->CreateSolidColorBrush (convert (state.fontColor, state.globalAlpha), + state.fontBrush.adoptPtr ()); + } + + //----------------------------------------------------------------------------- + CPoint lineWidthTransformMatrixOffset () const + { + int32_t lineWidthInt = static_cast (state.lineWidth); + if (static_cast (lineWidthInt) == state.lineWidth && lineWidthInt % 2) + { + return {0.5, 0.5}; + } + return {}; + } + + //----------------------------------------------------------------------------- + void applyLineStyle () + { + if (state.strokeStyle) + return; + + COM::Ptr factory {}; + deviceContext->GetFactory (factory.adoptPtr ()); + + D2D1_STROKE_STYLE_PROPERTIES properties; + switch (state.lineStyle.getLineCap ()) + { + case CLineStyle::kLineCapButt: + properties.startCap = properties.endCap = properties.dashCap = D2D1_CAP_STYLE_FLAT; + break; + case CLineStyle::kLineCapRound: + properties.startCap = properties.endCap = properties.dashCap = D2D1_CAP_STYLE_ROUND; + break; + case CLineStyle::kLineCapSquare: + properties.startCap = properties.endCap = properties.dashCap = + D2D1_CAP_STYLE_SQUARE; + break; + } + switch (state.lineStyle.getLineJoin ()) + { + case CLineStyle::kLineJoinMiter: + properties.lineJoin = D2D1_LINE_JOIN_MITER; + break; + case CLineStyle::kLineJoinRound: + properties.lineJoin = D2D1_LINE_JOIN_ROUND; + break; + case CLineStyle::kLineJoinBevel: + properties.lineJoin = D2D1_LINE_JOIN_BEVEL; + break; + } + properties.dashOffset = static_cast (state.lineStyle.getDashPhase ()); + properties.miterLimit = 10.f; + if (state.lineStyle.getDashCount ()) + { + properties.dashStyle = D2D1_DASH_STYLE_CUSTOM; + FLOAT* lengths = new FLOAT[state.lineStyle.getDashCount ()]; + for (uint32_t i = 0; i < state.lineStyle.getDashCount (); i++) + lengths[i] = static_cast (state.lineStyle.getDashLengths ()[i]); + factory->CreateStrokeStyle (properties, lengths, state.lineStyle.getDashCount (), + state.strokeStyle.adoptPtr ()); + delete[] lengths; + } + else + { + properties.dashStyle = D2D1_DASH_STYLE_SOLID; + factory->CreateStrokeStyle (properties, nullptr, 0, state.strokeStyle.adoptPtr ()); + } + } + + struct State + { + CRect clip {}; + CLineStyle lineStyle {kLineSolid}; + CDrawMode drawMode {}; + COM::Ptr strokeStyle; + COM::Ptr fillBrush; + COM::Ptr frameBrush; + COM::Ptr fontBrush; + CColor fillColor {kTransparentCColor}; + CColor frameColor {kTransparentCColor}; + CColor fontColor {kTransparentCColor}; + CCoord lineWidth {1.}; + double globalAlpha {1.}; + TransformMatrix tm {}; + }; + State state; + std::stack stateStack; + double scaleFactor {1.}; + CRect applyClip {}; + bool beginDrawCalled {false}; + TransformMatrix globalTM; + + const D2DGraphicsDevice& device; + COM::Ptr deviceContext; + +#if defined(VSTGUI_TEXTRENDERING_LEGACY_INCONSISTENCY) && \ + VSTGUI_TEXTRENDERING_LEGACY_INCONSISTENCY == 1 + static constexpr auto antialiasMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; +#else + static constexpr auto antialiasMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; +#endif +}; + +//------------------------------------------------------------------------ +D2DGraphicsDeviceContext::D2DGraphicsDeviceContext (const D2DGraphicsDevice& device, + ID2D1DeviceContext* deviceContext, + const TransformMatrix& tm) +{ + impl = std::make_unique (device, deviceContext, tm); +} + +//------------------------------------------------------------------------ +D2DGraphicsDeviceContext::~D2DGraphicsDeviceContext () noexcept { endDraw (); } + +//------------------------------------------------------------------------ +ID2D1DeviceContext* D2DGraphicsDeviceContext::getID2D1DeviceContext () const +{ + return impl->deviceContext.get (); +} + +//------------------------------------------------------------------------ +const IPlatformGraphicsDevice& D2DGraphicsDeviceContext::getDevice () const { return impl->device; } + +//------------------------------------------------------------------------ +PlatformGraphicsPathFactoryPtr D2DGraphicsDeviceContext::getGraphicsPathFactory () const +{ + return D2DGraphicsPathFactory::instance (); +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::beginDraw () const +{ + impl->beginDrawCalled = true; + impl->deviceContext->BeginDraw (); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::endDraw () const +{ + if (impl->applyClip.isEmpty () == false) + impl->deviceContext->PopAxisAlignedClip (); + impl->applyClip = {}; + if (impl->beginDrawCalled) + { + auto hr = impl->deviceContext->EndDraw (); + vstgui_assert (SUCCEEDED (hr)); + impl->beginDrawCalled = false; + } + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawLine (LinePair line) const +{ + impl->doInContext ( + [&] (auto deviceContext) { + impl->applyFrameColor (); + impl->applyLineStyle (); + + CPoint start (line.first); + CPoint end (line.second); + if (impl->state.drawMode.integralMode ()) + { + pixelAlign (impl->state.tm, start); + pixelAlign (impl->state.tm, end); + } + deviceContext->DrawLine (convert (start), convert (end), impl->state.frameBrush.get (), + static_cast (impl->state.lineWidth), + impl->state.strokeStyle.get ()); + }, + impl->lineWidthTransformMatrixOffset ()); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawLines (const LineList& lines) const +{ + impl->doInContext ( + [&] (auto deviceContext) { + impl->applyFrameColor (); + impl->applyLineStyle (); + + if (impl->state.drawMode.integralMode ()) + { + for (const auto& line : lines) + { + CPoint start (line.first); + CPoint end (line.second); + pixelAlign (impl->state.tm, start); + pixelAlign (impl->state.tm, end); + deviceContext->DrawLine ( + convert (start), convert (end), impl->state.frameBrush.get (), + static_cast (impl->state.lineWidth), impl->state.strokeStyle.get ()); + } + } + else + { + for (const auto& line : lines) + { + CPoint start (line.first); + CPoint end (line.second); + deviceContext->DrawLine ( + convert (start), convert (end), impl->state.frameBrush.get (), + static_cast (impl->state.lineWidth), impl->state.strokeStyle.get ()); + } + } + }, + impl->lineWidthTransformMatrixOffset ()); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawPolygon (const PointList& polygonPointList, + PlatformGraphicsDrawStyle drawStyle) const +{ + auto path = getGraphicsPathFactory ()->createPath (); + if (!path) + return false; + + path->beginSubpath (polygonPointList[0]); + for (uint32_t i = 1; i < polygonPointList.size (); ++i) + { + path->addLine (polygonPointList[i]); + } + if (drawStyle == PlatformGraphicsDrawStyle::Filled || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + { + drawGraphicsPath (*path, PlatformGraphicsPathDrawMode::Filled, nullptr); + } + if (drawStyle == PlatformGraphicsDrawStyle::Stroked || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + { + drawGraphicsPath (*path, PlatformGraphicsPathDrawMode::Stroked, nullptr); + } + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInContext ([&] (auto deviceContext) { + if (drawStyle != PlatformGraphicsDrawStyle::Filled) + { + rect.right -= 1.; + rect.bottom -= 1.; + } + if (drawStyle == PlatformGraphicsDrawStyle::Filled || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + { + impl->applyFillColor (); + deviceContext->FillRectangle (convert (rect), impl->state.fillBrush.get ()); + } + if (drawStyle == PlatformGraphicsDrawStyle::Stroked || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + { + rect.offset (impl->lineWidthTransformMatrixOffset ()); + impl->applyFrameColor (); + impl->applyLineStyle (); + deviceContext->DrawRectangle (convert (rect), impl->state.frameBrush.get (), + static_cast (impl->state.lineWidth), + impl->state.strokeStyle.get ()); + } + }); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawArc (CRect rect, double startAngle1, double endAngle2, + PlatformGraphicsDrawStyle drawStyle) const +{ + auto path = getGraphicsPathFactory ()->createPath (); + if (!path) + return false; + if (impl->state.drawMode.integralMode ()) + pixelAlign (impl->state.tm, rect); + path->addArc (rect, startAngle1, endAngle2, true); + if (drawStyle == PlatformGraphicsDrawStyle::Filled || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + drawGraphicsPath (*path, PlatformGraphicsPathDrawMode::Filled, nullptr); + if (drawStyle == PlatformGraphicsDrawStyle::Stroked || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + drawGraphicsPath (*path, PlatformGraphicsPathDrawMode::Stroked, nullptr); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const +{ + impl->doInContext ([&] (auto deviceContext) { + if (impl->state.drawMode.integralMode ()) + pixelAlign (impl->state.tm, rect); + if (drawStyle == PlatformGraphicsDrawStyle::Stroked) + rect.inset (0.5, 0.5); + CPoint center (rect.getTopLeft ()); + center.offset (rect.getWidth () / 2., rect.getHeight () / 2.); + D2D1_ELLIPSE ellipse; + ellipse.point = convert (center); + ellipse.radiusX = (FLOAT)(rect.getWidth () / 2.); + ellipse.radiusY = (FLOAT)(rect.getHeight () / 2.); + if (drawStyle == PlatformGraphicsDrawStyle::Filled || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + { + impl->applyFillColor (); + deviceContext->FillEllipse (ellipse, impl->state.fillBrush.get ()); + } + if (drawStyle == PlatformGraphicsDrawStyle::Stroked || + drawStyle == PlatformGraphicsDrawStyle::FilledAndStroked) + { + impl->applyFrameColor (); + impl->applyLineStyle (); + deviceContext->DrawEllipse (ellipse, impl->state.frameBrush.get (), + static_cast (impl->state.lineWidth), + impl->state.strokeStyle.get ()); + } + }); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawPoint (CPoint point, CColor color) const { return false; } + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, + double alpha, BitmapInterpolationQuality quality) const +{ + D2DBitmap* d2dBitmap = dynamic_cast (&bitmap); + if (!d2dBitmap || !d2dBitmap->getSource ()) + return false; + auto d2d1Bitmap = + D2DBitmapCache::getBitmap (d2dBitmap, impl->deviceContext.get (), impl->device.get ()); + if (!d2d1Bitmap) + return false; + + auto originalClip = impl->state.clip; + auto cr = dest; + impl->state.tm.transform (cr); + cr.bound (originalClip); + impl->state.clip = cr; + + double bitmapScaleFactor = d2dBitmap->getScaleFactor (); + CGraphicsTransform bitmapTransform; + bitmapTransform.scale (1. / bitmapScaleFactor, 1. / bitmapScaleFactor); + auto originalTransformMatrix = impl->state.tm; + TransformMatrix tm = originalTransformMatrix * bitmapTransform; + setTransformMatrix (tm); + auto invBitmapTransform = bitmapTransform.inverse (); + invBitmapTransform.transform (dest); + invBitmapTransform.transform (offset); + + impl->doInContext ([&] (auto deviceContext) { + auto bitmapSize = bitmap.getSize (); + CRect d (dest); + d.setWidth (bitmapSize.x); + d.setHeight (bitmapSize.y); + d.offset (-offset.x, -offset.y); + d.makeIntegral (); + CRect source; + source.setWidth (d2d1Bitmap->GetSize ().width); + source.setHeight (d2d1Bitmap->GetSize ().height); + + D2D1_BITMAP_INTERPOLATION_MODE mode; + switch (quality) + { + case BitmapInterpolationQuality::kLow: + mode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + break; + + case BitmapInterpolationQuality::kMedium: + case BitmapInterpolationQuality::kHigh: + default: + mode = D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; + break; + } + + D2D1_RECT_F sourceRect = convert (source); + deviceContext->DrawBitmap (d2d1Bitmap, convert (d), + static_cast (alpha * impl->state.globalAlpha), mode, + &sourceRect); + }); + setTransformMatrix (originalTransformMatrix); + impl->state.clip = originalClip; + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::clearRect (CRect rect) const +{ +#if 1 + TransformGuard tmGuard (impl->deviceContext.get ()); + + impl->deviceContext->SetTransform (convert (impl->globalTM * impl->state.tm)); + impl->deviceContext->PushAxisAlignedClip (convert (rect), D2D1_ANTIALIAS_MODE_ALIASED); + impl->deviceContext->Clear (D2D1::ColorF (1.f, 1.f, 1.f, 0.f)); + impl->deviceContext->PopAxisAlignedClip (); +#else + CRect oldClip = impl->state.clip; + rect.bound (oldClip); + setClipRect (rect); + impl->doInContext ( + [] (auto deviceContext) { deviceContext->Clear (D2D1::ColorF (1.f, 1.f, 1.f, 0.f)); }); + setClipRect (oldClip); +#endif + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawGraphicsPath (IPlatformGraphicsPath& path, + PlatformGraphicsPathDrawMode mode, + TransformMatrix* transformation) const +{ + auto d2dPath = dynamic_cast (&path); + if (d2dPath == nullptr) + return false; + + impl->doInContext ([&] (auto deviceContext) { + COM::Ptr path; + if (transformation) + { + COM::Ptr factory {}; + deviceContext->GetFactory (factory.adoptPtr ()); + path = COM::adopt ( + d2dPath->createTransformedGeometry (factory.get (), *transformation)); + } + else + { + path = COM::share (d2dPath->getPathGeometry ()); + } + if (path) + { + if (mode == PlatformGraphicsPathDrawMode::Filled || + mode == PlatformGraphicsPathDrawMode::FilledEvenOdd) + { + impl->applyFillColor (); + deviceContext->FillGeometry (path.get (), impl->state.fillBrush.get ()); + } + else if (mode == PlatformGraphicsPathDrawMode::Stroked) + { + impl->applyFrameColor (); + impl->applyLineStyle (); + deviceContext->DrawGeometry (path.get (), impl->state.frameBrush.get (), + (FLOAT)impl->state.lineWidth, + impl->state.strokeStyle.get ()); + } + } + }); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::fillLinearGradient (IPlatformGraphicsPath& path, + const IPlatformGradient& gradient, + CPoint startPoint, CPoint endPoint, bool evenOdd, + TransformMatrix* transformation) const +{ + auto d2dPath = dynamic_cast (&path); + const auto d2dGradient = dynamic_cast (&gradient); + if (d2dPath == nullptr || d2dGradient == nullptr) + return false; + impl->doInContext ([&] (auto deviceContext) { + auto stopCollection = COM::adopt ( + d2dGradient->create (deviceContext, static_cast (impl->state.globalAlpha))); + if (!stopCollection) + return; + COM::Ptr path; + if (transformation) + { + COM::Ptr factory {}; + deviceContext->GetFactory (factory.adoptPtr ()); + path = COM::adopt ( + d2dPath->createTransformedGeometry (factory.get (), *transformation)); + } + else + { + path = COM::share (d2dPath->getPathGeometry ()); + } + if (path) + { + COM::Ptr brush; + D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES properties; + properties.startPoint = convert (startPoint); + properties.endPoint = convert (endPoint); + if (SUCCEEDED (deviceContext->CreateLinearGradientBrush ( + properties, stopCollection.get (), brush.adoptPtr ()))) + { + deviceContext->FillGeometry (path.get (), brush.get ()); + } + } + }); + return true; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::fillRadialGradient (IPlatformGraphicsPath& path, + const IPlatformGradient& gradient, CPoint center, + CCoord radius, CPoint originOffset, bool evenOdd, + TransformMatrix* transformation) const +{ + auto d2dPath = dynamic_cast (&path); + const auto d2dGradient = dynamic_cast (&gradient); + if (d2dPath == nullptr || d2dGradient == nullptr) + return false; + impl->doInContext ([&] (auto deviceContext) { + auto stopCollection = COM::adopt ( + d2dGradient->create (deviceContext, static_cast (impl->state.globalAlpha))); + if (!stopCollection) + return; + COM::Ptr path; + if (transformation) + { + COM::Ptr factory {}; + deviceContext->GetFactory (factory.adoptPtr ()); + path = COM::adopt ( + d2dPath->createTransformedGeometry (factory.get (), *transformation)); + } + else + { + path = COM::share (d2dPath->getPathGeometry ()); + } + if (path) + { + COM::Ptr brush; + D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES properties; + properties.center = convert (center); + properties.gradientOriginOffset = convert (originOffset); + properties.radiusX = (FLOAT)radius; + properties.radiusY = (FLOAT)radius; + if (SUCCEEDED (deviceContext->CreateRadialGradientBrush ( + properties, stopCollection.get (), brush.adoptPtr ()))) + { + deviceContext->FillGeometry (path.get (), brush.get ()); + } + } + }); + return true; +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::saveGlobalState () const { impl->stateStack.push (impl->state); } + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::restoreGlobalState () const +{ + vstgui_assert (impl->stateStack.empty () == false, + "Unbalanced calls to saveGlobalState and restoreGlobalState"); +#if NDEBUG + if (impl->stateStack.empty ()) + return; +#endif + impl->state = impl->stateStack.top (); + impl->stateStack.pop (); +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setLineStyle (const CLineStyle& style) const +{ + if (impl->state.lineStyle != style) + { + impl->state.lineStyle = style; + impl->state.strokeStyle.reset (); + } +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setLineWidth (CCoord width) const { impl->state.lineWidth = width; } + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setDrawMode (CDrawMode mode) const { impl->state.drawMode = mode; } + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setClipRect (CRect clip) const { impl->state.clip = clip; } + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setFillColor (CColor color) const +{ + if (impl->state.fillColor != color) + { + impl->state.fillColor = color; + impl->state.fillBrush.reset (); + } +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setFrameColor (CColor color) const +{ + if (impl->state.frameColor != color) + { + impl->state.frameColor = color; + impl->state.frameBrush.reset (); + } +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setGlobalAlpha (double newAlpha) const +{ + if (impl->state.globalAlpha != newAlpha) + { + impl->state.globalAlpha = newAlpha; + impl->state.fillBrush.reset (); + impl->state.frameBrush.reset (); + impl->state.fontBrush.reset (); + } +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::setTransformMatrix (const TransformMatrix& tm) const +{ + impl->state.tm = tm; +} + +//------------------------------------------------------------------------ +const IPlatformGraphicsDeviceContextBitmapExt* D2DGraphicsDeviceContext::asBitmapExt () const +{ + return this; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::drawBitmapNinePartTiled (IPlatformBitmap& bitmap, CRect dest, + const CNinePartTiledDescription& desc, + double alpha, + BitmapInterpolationQuality quality) const +{ + return false; +} + +//------------------------------------------------------------------------ +bool D2DGraphicsDeviceContext::fillRectWithBitmap (IPlatformBitmap& bitmap, CRect srcRect, + CRect dstRect, double alpha, + BitmapInterpolationQuality quality) const +{ + D2DBitmap* d2dBitmap = dynamic_cast (&bitmap); + if (!d2dBitmap || !d2dBitmap->getSource ()) + return false; + auto d2d1Bitmap = + D2DBitmapCache::getBitmap (d2dBitmap, impl->deviceContext.get (), impl->device.get ()); + if (!d2d1Bitmap) + return false; + + auto originalClip = impl->state.clip; + auto cr = dstRect; + impl->state.tm.transform (cr); + cr.bound (originalClip); + + double bitmapScaleFactor = d2dBitmap->getScaleFactor (); + CGraphicsTransform bitmapTransform; + bitmapTransform.scale (1. / bitmapScaleFactor, 1. / bitmapScaleFactor); + auto invBitmapTransform = bitmapTransform.inverse (); + invBitmapTransform.transform (dstRect); + invBitmapTransform.transform (srcRect); + + D2D1_IMAGE_BRUSH_PROPERTIES imageBrushProp = {}; + imageBrushProp.sourceRectangle = convert (srcRect); + imageBrushProp.extendModeX = imageBrushProp.extendModeY = D2D1_EXTEND_MODE_WRAP; + switch (quality) + { + case BitmapInterpolationQuality::kLow: + imageBrushProp.interpolationMode = D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + break; + + case BitmapInterpolationQuality::kMedium: + case BitmapInterpolationQuality::kHigh: + default: + imageBrushProp.interpolationMode = D2D1_INTERPOLATION_MODE_LINEAR; + break; + } + CGraphicsTransform brushTransform; + brushTransform.translate (dstRect.getTopLeft ()); + + D2D1_BRUSH_PROPERTIES brushProp = {}; + brushProp.opacity = 1.f; + brushProp.transform = convert (brushTransform); + + COM::Ptr brush; + auto hr = impl->deviceContext->CreateImageBrush (d2d1Bitmap, imageBrushProp, brushProp, + brush.adoptPtr ()); + if (FAILED (hr)) + return false; + + impl->state.clip = cr; + + auto originalTransformMatrix = impl->state.tm; + TransformMatrix tm = originalTransformMatrix * bitmapTransform; + setTransformMatrix (tm); + + impl->doInContext ([&] (auto deviceContext) { + deviceContext->FillRectangle (convert (dstRect), brush.get ()); + }); + + setTransformMatrix (originalTransformMatrix); + impl->state.clip = originalClip; + return true; +} + +//------------------------------------------------------------------------ +void D2DGraphicsDeviceContext::drawTextLayout (IDWriteTextLayout* textLayout, CPoint pos, + CColor color, bool antialias) +{ + impl->doInContext ([&] (auto deviceContext) { + deviceContext->SetTextAntialiasMode (antialias ? impl->antialiasMode + : D2D1_TEXT_ANTIALIAS_MODE_ALIASED); + if (impl->state.drawMode.integralMode ()) + pos.makeIntegral (); + pos.y += 0.5; + impl->applyFontColor (color); + deviceContext->DrawTextLayout (convert (pos), textLayout, impl->state.fontBrush.get ()); + }); +} + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/win32/direct2d/d2dgraphicscontext.h b/vstgui/lib/platform/win32/direct2d/d2dgraphicscontext.h new file mode 100644 index 000000000..b01373e1d --- /dev/null +++ b/vstgui/lib/platform/win32/direct2d/d2dgraphicscontext.h @@ -0,0 +1,123 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE + +#pragma once + +#include "../../iplatformgraphicsdevice.h" + +struct ID2D1DeviceContext; +struct ID2D1Device; +struct ID2D1SolidColorBrush; +struct IDWriteTextLayout; + +//------------------------------------------------------------------------ +namespace VSTGUI { + +class D2DGraphicsDevice; + +//------------------------------------------------------------------------ +class D2DGraphicsDeviceContext : public IPlatformGraphicsDeviceContext, + public IPlatformGraphicsDeviceContextBitmapExt +{ +public: + D2DGraphicsDeviceContext (const D2DGraphicsDevice& device, ID2D1DeviceContext* deviceContext, + const TransformMatrix& tm); + ~D2DGraphicsDeviceContext () noexcept; + + const IPlatformGraphicsDevice& getDevice () const override; + PlatformGraphicsPathFactoryPtr getGraphicsPathFactory () const override; + + bool beginDraw () const override; + bool endDraw () const override; + // draw commands + bool drawLine (LinePair line) const override; + bool drawLines (const LineList& lines) const override; + bool drawPolygon (const PointList& polygonPointList, + PlatformGraphicsDrawStyle drawStyle) const override; + bool drawRect (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override; + bool drawArc (CRect rect, double startAngle1, double endAngle2, + PlatformGraphicsDrawStyle drawStyle) const override; + bool drawEllipse (CRect rect, PlatformGraphicsDrawStyle drawStyle) const override; + bool drawPoint (CPoint point, CColor color) const override; + bool drawBitmap (IPlatformBitmap& bitmap, CRect dest, CPoint offset, double alpha, + BitmapInterpolationQuality quality) const override; + bool clearRect (CRect rect) const override; + bool drawGraphicsPath (IPlatformGraphicsPath& path, PlatformGraphicsPathDrawMode mode, + TransformMatrix* transformation) const override; + bool fillLinearGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint startPoint, CPoint endPoint, bool evenOdd, + TransformMatrix* transformation) const override; + bool fillRadialGradient (IPlatformGraphicsPath& path, const IPlatformGradient& gradient, + CPoint center, CCoord radius, CPoint originOffset, bool evenOdd, + TransformMatrix* transformation) const override; + // state + void saveGlobalState () const override; + void restoreGlobalState () const override; + void setLineStyle (const CLineStyle& style) const override; + void setLineWidth (CCoord width) const override; + void setDrawMode (CDrawMode mode) const override; + void setClipRect (CRect clip) const override; + void setFillColor (CColor color) const override; + void setFrameColor (CColor color) const override; + void setGlobalAlpha (double newAlpha) const override; + void setTransformMatrix (const TransformMatrix& tm) const override; + + // extension + const IPlatformGraphicsDeviceContextBitmapExt* asBitmapExt () const override; + + bool drawBitmapNinePartTiled (IPlatformBitmap& bitmap, CRect dest, + const CNinePartTiledDescription& desc, double alpha, + BitmapInterpolationQuality quality) const override; + bool fillRectWithBitmap (IPlatformBitmap& bitmap, CRect srcRect, CRect dstRect, double alpha, + BitmapInterpolationQuality quality) const override; + + // private + void drawTextLayout (IDWriteTextLayout* textLayout, CPoint pos, CColor color, bool antialias); + + ID2D1DeviceContext* getID2D1DeviceContext () const; + +protected: +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +class D2DGraphicsDevice : public IPlatformGraphicsDevice +{ +public: + D2DGraphicsDevice (ID2D1Device* device); + ~D2DGraphicsDevice () noexcept; + + PlatformGraphicsDeviceContextPtr + createBitmapContext (const PlatformBitmapPtr& bitmap) const override; + + ID2D1Device* get () const; + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +class D2DGraphicsDeviceFactory : public IPlatformGraphicsDeviceFactory +{ +public: + D2DGraphicsDeviceFactory (); + ~D2DGraphicsDeviceFactory () noexcept; + + PlatformGraphicsDevicePtr getDeviceForScreen (ScreenInfo::Identifier screen) const override; + + PlatformGraphicsDevicePtr find (ID2D1Device* dev) const; + + void addDevice (const std::shared_ptr& device) const; + void removeDevice (const std::shared_ptr& device) const; + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +} // VSTGUI diff --git a/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp b/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp index 5349668a6..bd2a9ce87 100644 --- a/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp +++ b/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp @@ -9,8 +9,8 @@ #include "../../../cgradient.h" #include "../../../cstring.h" #include "../win32support.h" -#include "d2ddrawcontext.h" #include "d2dfont.h" +#include "d2d.h" #include #include #include @@ -103,132 +103,6 @@ class D2DPathTextRenderer final : public IDWriteTextRenderer ID2D1GeometrySink* sink; }; -//----------------------------------------------------------------------------- -class AlignPixelSink final : public ID2D1SimplifiedGeometrySink -{ -public: - HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, void** ppvObject) override - { - if (iid == __uuidof(IUnknown) || iid == __uuidof(ID2D1SimplifiedGeometrySink)) - { - *ppvObject = static_cast (this); - AddRef (); - return S_OK; - } - else - return E_NOINTERFACE; - } - ULONG STDMETHODCALLTYPE AddRef () override { return 1; } - ULONG STDMETHODCALLTYPE Release () override { return 1; } - - D2D1_POINT_2F alignPoint (const D2D1_POINT_2F& p) - { - CPoint point (p.x, p.y); - if (context->getDrawMode ().antiAliasing ()) - point.offset (-0.5, -0.5); - if (context) - context->pixelAllign (point); - return D2D1::Point2F (static_cast (point.x), static_cast (point.y)); - } - - STDMETHOD_ (void, AddBeziers) (const D2D1_BEZIER_SEGMENT* beziers, UINT beziersCount) override - { - for (UINT i = 0; i < beziersCount; ++i) - { - D2D1_BEZIER_SEGMENT segment = {}; - segment.point1 = alignPoint (beziers[i].point1); - segment.point2 = alignPoint (beziers[i].point2); - segment.point3 = alignPoint (beziers[i].point3); - sink->AddBezier (segment); - } - } - - STDMETHOD_ (void, AddLines) (const D2D1_POINT_2F* points, UINT pointsCount) override - { - for (UINT i = 0; i < pointsCount; ++i) - { - D2D_POINT_2F point = alignPoint (points[i]); - sink->AddLine (point); - } - } - - STDMETHOD_ (void, BeginFigure) - (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN figureBegin) override - { - startPoint = alignPoint (startPoint); - sink->BeginFigure (startPoint, figureBegin); - } - - STDMETHOD_ (void, EndFigure) (D2D1_FIGURE_END figureEnd) override - { - sink->EndFigure (figureEnd); - } - - STDMETHOD_ (void, SetFillMode) (D2D1_FILL_MODE fillMode) override - { - sink->SetFillMode (fillMode); - } - - STDMETHOD_ (void, SetSegmentFlags) (D2D1_PATH_SEGMENT vertexFlags) override - { - sink->SetSegmentFlags (vertexFlags); - } - - STDMETHOD (Close) () override - { - isClosed = true; - return sink->Close (); - } - - AlignPixelSink (D2DDrawContext* context) - : path (nullptr), sink (nullptr), context (context), isClosed (true) - { - } - - ~AlignPixelSink () - { - if (sink) - sink->Release (); - if (path) - path->Release (); - } - - bool init () - { - getD2DFactory ()->CreatePathGeometry (&path); - if (path == nullptr) - return false; - if (!SUCCEEDED (path->Open (&sink))) - return false; - isClosed = false; - return true; - } - - ID2D1PathGeometry* get () - { - if (path) - { - if (sink) - { - if (!isClosed) - sink->Close (); - sink->Release (); - sink = nullptr; - } - ID2D1PathGeometry* result = path; - path = nullptr; - return result; - } - return nullptr; - } - -private: - ID2D1PathGeometry* path; - ID2D1GeometrySink* sink; - D2DDrawContext* context; - bool isClosed; -}; - //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -256,33 +130,6 @@ ID2D1Geometry* D2DGraphicsPath::createTransformedGeometry (ID2D1Factory* factory ; } -//----------------------------------------------------------------------------- -ID2D1Geometry* D2DGraphicsPath::createPixelAlignedGeometry (ID2D1Factory* factory, - D2DDrawContext& context, - const CGraphicsTransform* tm) const -{ - ID2D1Geometry* workingPath = path; - workingPath->AddRef (); - if (tm) - workingPath = createTransformedGeometry (factory, *tm); - - AlignPixelSink alignSink (&context); - if (alignSink.init () == false) - { - workingPath->Release (); - return nullptr; - } - if (!SUCCEEDED (workingPath->Simplify (D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, - nullptr, &alignSink))) - { - workingPath->Release (); - return nullptr; - } - workingPath->Release (); - - return alignSink.get (); -} - //----------------------------------------------------------------------------- ID2D1GeometrySink* D2DGraphicsPath::getSink () { @@ -324,12 +171,12 @@ void D2DGraphicsPath::addArc (const CRect& rect, double startAngle, double endAn start.y = r.top + center.y + center.y * sin (startAngle); if (!figureOpen) { - sink->BeginFigure (makeD2DPoint (start), D2D1_FIGURE_BEGIN_FILLED); + sink->BeginFigure (convert (start), D2D1_FIGURE_BEGIN_FILLED); figureOpen = true; } else if (lastPos != start) { - sink->AddLine (makeD2DPoint (start)); + sink->AddLine (convert (start)); } double sweepangle = endAngle - startAngle; @@ -355,11 +202,12 @@ void D2DGraphicsPath::addArc (const CRect& rect, double startAngle, double endAn endPoint.y = r.top + center.y + center.y * sin (endAngle); D2D1_ARC_SEGMENT arc; - arc.size = makeD2DSize (r.getWidth () / 2., r.getHeight () / 2.); + arc.size = {static_cast (r.getWidth () / 2.), + static_cast (r.getHeight () / 2.)}; arc.rotationAngle = 0; arc.sweepDirection = clockwise ? D2D1_SWEEP_DIRECTION_CLOCKWISE : D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; - arc.point = makeD2DPoint (endPoint); + arc.point = convert (endPoint); arc.arcSize = fabs (sweepangle) <= M_PI ? D2D1_ARC_SIZE_SMALL : D2D1_ARC_SIZE_LARGE; sink->AddArc (arc); lastPos = endPoint; @@ -382,16 +230,16 @@ void D2DGraphicsPath::addEllipse (const CRect& rect) } if (!figureOpen) { - sink->BeginFigure (makeD2DPoint (top), D2D1_FIGURE_BEGIN_FILLED); + sink->BeginFigure (convert (top), D2D1_FIGURE_BEGIN_FILLED); figureOpen = true; } D2D1_ARC_SEGMENT arc = - D2D1::ArcSegment (makeD2DPoint (bottom), - D2D1::SizeF (static_cast (rect.getWidth () / 2.f), - static_cast (rect.getHeight () / 2.f)), - 180.f, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL); + D2D1::ArcSegment (convert (bottom), + D2D1::SizeF (static_cast (rect.getWidth () / 2.f), + static_cast (rect.getHeight () / 2.f)), + 180.f, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL); sink->AddArc (arc); - arc.point = makeD2DPoint (top); + arc.point = convert (top); sink->AddArc (arc); lastPos = top; } @@ -492,6 +340,7 @@ void D2DGraphicsPath::finishBuilding () sink->EndFigure (D2D1_FIGURE_END_OPEN); HRESULT res = sink->Close (); assert (SUCCEEDED (res)); + (void)res; // prevent warning in release builds sink->Release (); sinkInternal = nullptr; figureOpen = false; @@ -513,7 +362,7 @@ bool D2DGraphicsPath::hitTest (const CPoint& p, bool evenOddFilled, matrix._32 = (FLOAT)transform->dy; } BOOL result = false; - path->FillContainsPoint (makeD2DPoint (p), matrix, &result); + path->FillContainsPoint (convert (p), matrix, &result); return result ? true : false; } diff --git a/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.h b/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.h index fb67ccd52..1149b1369 100644 --- a/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.h +++ b/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.h @@ -42,8 +42,6 @@ class D2DGraphicsPath : public IPlatformGraphicsPath ID2D1PathGeometry* getPathGeometry () const { return path; } ID2D1Geometry* createTransformedGeometry (ID2D1Factory* factory, const CGraphicsTransform& tm) const; - ID2D1Geometry* createPixelAlignedGeometry (ID2D1Factory* factory, D2DDrawContext& context, - const CGraphicsTransform* tm = nullptr) const; // IPlatformGraphicsPath void addArc (const CRect& rect, double startAngle, double endAngle, bool clockwise) override; diff --git a/vstgui/lib/platform/win32/win32directcomposition.cpp b/vstgui/lib/platform/win32/win32directcomposition.cpp index 75d47cb63..cef864410 100644 --- a/vstgui/lib/platform/win32/win32directcomposition.cpp +++ b/vstgui/lib/platform/win32/win32directcomposition.cpp @@ -821,6 +821,12 @@ bool Factory::removeVisual (const VisualPtr& visualPtr) return false; } +//------------------------------------------------------------------------ +ID2D1Device* Factory::getDevice () const +{ + return impl->d2dDevice.get (); +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- diff --git a/vstgui/lib/platform/win32/win32directcomposition.h b/vstgui/lib/platform/win32/win32directcomposition.h index 7ba9b6ac1..56f1b27f8 100644 --- a/vstgui/lib/platform/win32/win32directcomposition.h +++ b/vstgui/lib/platform/win32/win32directcomposition.h @@ -12,6 +12,7 @@ #include interface ID2D1DeviceContext; +interface ID2D1Device; //----------------------------------------------------------------------------- namespace VSTGUI { @@ -47,6 +48,8 @@ struct Factory VisualPtr createChildVisual (const VisualPtr& parent, uint32_t width, uint32_t height); bool removeVisual (const VisualPtr& visual); + ID2D1Device* getDevice () const; + ~Factory () noexcept; struct Impl; diff --git a/vstgui/lib/platform/win32/win32dragging.cpp b/vstgui/lib/platform/win32/win32dragging.cpp index 864a9a6e2..ad84ff2b5 100644 --- a/vstgui/lib/platform/win32/win32dragging.cpp +++ b/vstgui/lib/platform/win32/win32dragging.cpp @@ -4,9 +4,10 @@ #include "win32dragging.h" #include "win32support.h" +#include "win32factory.h" #include "winstring.h" -#include "../../cstring.h" #include "../../cdrawcontext.h" +#include "../../cstring.h" #include "../../cvstguitimer.h" #include "win32dll.h" #include @@ -786,7 +787,7 @@ void Win32DragBitmapWindow::updateWindowPosition (POINT where) void Win32DragBitmapWindow::paint () { PAINTSTRUCT ps; - HDC hdc = BeginPaint (hwnd, &ps); + BeginPaint (hwnd, &ps); RECT clientRect; GetClientRect (hwnd, &clientRect); @@ -795,17 +796,22 @@ void Win32DragBitmapWindow::paint () rect.setWidth (clientRect.right - clientRect.left); rect.setHeight (clientRect.bottom - clientRect.top); - if (auto drawContext = owned (createDrawContext (hwnd, hdc, rect))) + if (auto deviceContext = + getPlatformFactory ().asWin32Factory ()->createGraphicsDeviceContext (hwnd)) { - drawContext->beginDraw (); + CDrawContext drawContext (deviceContext, rect, 1.); - drawContext->clearRect (rect); - drawContext->setGlobalAlpha (0.9f); - CDrawContext::Transform t (*drawContext, CGraphicsTransform ().scale (scaleFactor, scaleFactor)); - bitmap->draw (drawContext, rect); + drawContext.beginDraw (); - drawContext->endDraw (); + drawContext.clearRect (rect); + drawContext.setGlobalAlpha (0.9f); + CDrawContext::Transform t (drawContext, + CGraphicsTransform ().scale (scaleFactor, scaleFactor)); + bitmap->draw (&drawContext, rect); + + drawContext.endDraw (); } + EndPaint (hwnd, &ps); } diff --git a/vstgui/lib/platform/win32/win32factory.cpp b/vstgui/lib/platform/win32/win32factory.cpp index 02e7153f3..1b17040f1 100644 --- a/vstgui/lib/platform/win32/win32factory.cpp +++ b/vstgui/lib/platform/win32/win32factory.cpp @@ -8,13 +8,14 @@ #include "../iplatformfont.h" #include "../iplatformframe.h" #include "../iplatformframecallback.h" +#include "../iplatformgraphicsdevice.h" #include "../iplatformresourceinputstream.h" #include "../iplatformstring.h" #include "../iplatformtimer.h" #include "../common/fileresourceinputstream.h" #include "direct2d/d2dbitmap.h" #include "direct2d/d2dbitmapcache.h" -#include "direct2d/d2ddrawcontext.h" +#include "direct2d/d2dgraphicscontext.h" #include "direct2d/d2dfont.h" #include "direct2d/d2dgradient.h" #include "win32frame.h" @@ -52,6 +53,7 @@ struct Win32Factory::Impl COM::Ptr wicImagingFactory; std::unique_ptr directCompositionFactory; + D2DGraphicsDeviceFactory graphicsDeviceFactory; UTF8String resourceBasePath; bool useD2DHardwareRenderer {false}; @@ -112,6 +114,13 @@ Win32Factory::Win32Factory (HINSTANCE instance) impl->directCompositionFactory = DirectComposition::Factory::create (impl->d2dFactory.get ()); D2DBitmapCache::init (); + if (impl->directCompositionFactory) + { + auto device = impl->directCompositionFactory->getDevice (); + vstgui_assert (device, "if there's a direct composition factory it must have a device"); + auto d2dDevice = std::make_shared (device); + impl->graphicsDeviceFactory.addDevice (d2dDevice); + } } //----------------------------------------------------------------------------- @@ -188,6 +197,56 @@ DirectComposition::Factory* Win32Factory::getDirectCompositionFactory () const n return impl->directCompositionFactory.get (); } +//----------------------------------------------------------------------------- +PlatformGraphicsDeviceContextPtr + Win32Factory::createGraphicsDeviceContext (void* hwnd) const noexcept +{ + auto window = reinterpret_cast (hwnd); + auto renderTargetType = useD2DHardwareRenderer () ? D2D1_RENDER_TARGET_TYPE_HARDWARE + : D2D1_RENDER_TARGET_TYPE_SOFTWARE; + RECT rc; + GetClientRect (window, &rc); + + auto size = D2D1::SizeU (static_cast (rc.right - rc.left), + static_cast (rc.bottom - rc.top)); + COM::Ptr hwndRenderTarget; + D2D1_PIXEL_FORMAT pixelFormat = + D2D1::PixelFormat (DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED); + HRESULT hr = getD2DFactory ()->CreateHwndRenderTarget ( + D2D1::RenderTargetProperties (renderTargetType, pixelFormat), + D2D1::HwndRenderTargetProperties (window, size, D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS), + hwndRenderTarget.adoptPtr ()); + if (FAILED (hr)) + return nullptr; + hwndRenderTarget->SetDpi (96, 96); + + COM::Ptr deviceContext; + hr = hwndRenderTarget->QueryInterface (__uuidof (ID2D1DeviceContext), + reinterpret_cast (deviceContext.adoptPtr ())); + if (FAILED (hr)) + return nullptr; + + ID2D1Device* d2ddevice {}; + deviceContext->GetDevice (&d2ddevice); + auto device = impl->graphicsDeviceFactory.find (d2ddevice); + if (!device) + { + impl->graphicsDeviceFactory.addDevice (std::make_shared (d2ddevice)); + device = impl->graphicsDeviceFactory.find (d2ddevice); + vstgui_assert (device); + } + + return std::make_shared ( + *std::static_pointer_cast (device).get (), deviceContext.get (), + TransformMatrix {}); +} + +//----------------------------------------------------------------------------- +void Win32Factory::disableDirectComposition () const noexcept +{ + impl->directCompositionFactory.reset (); +} + //----------------------------------------------------------------------------- uint64_t Win32Factory::getTicks () const noexcept { @@ -324,18 +383,6 @@ auto Win32Factory::getClipboard () const noexcept -> DataPackagePtr return makeOwned (dataObject); } -//------------------------------------------------------------------------ -auto Win32Factory::createOffscreenContext (const CPoint& size, double scaleFactor) const noexcept - -> COffscreenContextPtr -{ - if (auto bitmap = makeOwned (size * scaleFactor)) - { - bitmap->setScaleFactor (scaleFactor); - return owned (new D2DDrawContext (bitmap)); - } - return nullptr; -} - //----------------------------------------------------------------------------- PlatformGradientPtr Win32Factory::createGradient () const noexcept { @@ -350,6 +397,12 @@ PlatformFileSelectorPtr Win32Factory::createFileSelector (PlatformFileSelectorSt return createWinFileSelector (style, win32Frame ? win32Frame->getHWND () : nullptr); } +//----------------------------------------------------------------------------- +const IPlatformGraphicsDeviceFactory& Win32Factory::getGraphicsDeviceFactory () const noexcept +{ + return impl->graphicsDeviceFactory; +} + //----------------------------------------------------------------------------- const LinuxFactory* Win32Factory::asLinuxFactory () const noexcept { diff --git a/vstgui/lib/platform/win32/win32factory.h b/vstgui/lib/platform/win32/win32factory.h index 03b09b508..af83db82d 100644 --- a/vstgui/lib/platform/win32/win32factory.h +++ b/vstgui/lib/platform/win32/win32factory.h @@ -42,6 +42,11 @@ class Win32Factory final : public IPlatformFactory IDWriteFactory* getDirectWriteFactory () const noexcept; DirectComposition::Factory* getDirectCompositionFactory () const noexcept; + PlatformGraphicsDeviceContextPtr createGraphicsDeviceContext (void* hwnd) const noexcept; + + /** disable the use of direct composition. must be called before anything else or the behaviour + * is undefined. */ + void disableDirectComposition () const noexcept; /** Return platform ticks (millisecond resolution) * @return ticks @@ -133,14 +138,6 @@ class Win32Factory final : public IPlatformFactory */ DataPackagePtr getClipboard () const noexcept final; - /** create an offscreen draw device - * @param size the size of the bitmap where the offscreen renders to - * @param scaleFactor the scale factor for drawing - * @return an offscreen context object or nullptr on failure - */ - COffscreenContextPtr createOffscreenContext (const CPoint& size, - double scaleFactor = 1.) const noexcept final; - /** Create a platform gradient object * @return platform gradient object or nullptr on failure */ @@ -154,6 +151,12 @@ class Win32Factory final : public IPlatformFactory PlatformFileSelectorPtr createFileSelector (PlatformFileSelectorStyle style, IPlatformFrame* frame) const noexcept; + /** Get the graphics device factory + * + * @return platform graphics device factory + */ + const IPlatformGraphicsDeviceFactory& getGraphicsDeviceFactory () const noexcept final; + const LinuxFactory* asLinuxFactory () const noexcept final; const MacFactory* asMacFactory () const noexcept final; const Win32Factory* asWin32Factory () const noexcept final; diff --git a/vstgui/lib/platform/win32/win32frame.cpp b/vstgui/lib/platform/win32/win32frame.cpp index f17b2d13b..886100832 100644 --- a/vstgui/lib/platform/win32/win32frame.cpp +++ b/vstgui/lib/platform/win32/win32frame.cpp @@ -9,9 +9,9 @@ #include #include #include -#include "direct2d/d2ddrawcontext.h" #include "direct2d/d2dbitmap.h" #include "direct2d/d2dgraphicspath.h" +#include "direct2d/d2dgraphicscontext.h" #include "win32factory.h" #include "win32textedit.h" #include "win32optionmenu.h" @@ -71,13 +71,13 @@ static bool isParentLayered (HWND parent) } //----------------------------------------------------------------------------- -Win32Frame::Win32Frame (IPlatformFrameCallback* frame, const CRect& size, HWND parent, PlatformType parentType) +Win32Frame::Win32Frame (IPlatformFrameCallback* frame, const CRect& size, HWND parent, + PlatformType parentType) : IPlatformFrame (frame) , parentWindow (parent) , windowHandle (nullptr) , tooltipWindow (nullptr) , oldFocusWindow (nullptr) -, deviceContext (nullptr) , inPaint (false) , mouseInside (false) , updateRegionList (nullptr) @@ -129,12 +129,8 @@ Win32Frame::~Win32Frame () noexcept if (updateRegionList) std::free (updateRegionList); - if (deviceContext) - deviceContext->forget (); if (tooltipWindow) DestroyWindow (tooltipWindow); - if (backBuffer) - backBuffer = nullptr; if (windowHandle) RevokeDragDrop (windowHandle); if (parentWindow) @@ -285,15 +281,7 @@ bool Win32Frame::getGlobalPosition (CPoint& pos) const //----------------------------------------------------------------------------- bool Win32Frame::setSize (const CRect& newSize) { - if (deviceContext) - { - deviceContext->forget (); - deviceContext = nullptr; - } - if (backBuffer) - { - backBuffer = getPlatformFactory ().createOffscreenContext (newSize.getSize ()); - } + legacyDrawDevice.reset (); if (!parentWindow) return true; SetWindowPos (windowHandle, HWND_TOP, (int)newSize.left, (int)newSize.top, (int)newSize.getWidth (), (int)newSize.getHeight (), SWP_NOZORDER|SWP_NOCOPYBITS|SWP_NOREDRAW|SWP_DEFERERASE); @@ -657,22 +645,24 @@ void Win32Frame::paint (HWND hwnd) directCompositionVisual->resize (static_cast (frameSize.getWidth ()), static_cast (frameSize.getHeight ())); iterateRegion (rgn, [&] (const auto& rect) { - directCompositionVisual->update ( - rect, [&] (auto deviceContext, auto rect, auto offsetX, auto offsetY) { - COM::Ptr device; - deviceContext->GetDevice (device.adoptPtr ()); - D2DDrawContext drawContext (deviceContext, frameSize, device.get ()); - drawContext.setClipRect (rect); - CGraphicsTransform tm; - tm.translate (offsetX - rect.left, offsetY - rect.top); - CDrawContext::Transform transform (drawContext, tm); - { - drawContext.saveGlobalState (); - drawContext.clearRect (rect); - getFrame ()->platformDrawRect (&drawContext, rect); - drawContext.restoreGlobalState (); - } - }); + directCompositionVisual->update (rect, [&] (auto deviceContext, auto rect, + auto offsetX, auto offsetY) { + COM::Ptr device; + deviceContext->GetDevice (device.adoptPtr ()); + + CGraphicsTransform tm; + tm.translate (offsetX - rect.left, offsetY - rect.top); + + const auto& graphicsDeviceFactory = + static_cast ( + getPlatformFactory ().asWin32Factory ()->getGraphicsDeviceFactory ()); + auto graphicsDevice = graphicsDeviceFactory.find (device.get ()); + auto drawDevice = std::make_shared ( + *static_cast (graphicsDevice.get ()), deviceContext, + tm); + + getFrame ()->platformDrawRects (drawDevice, 1., {1, rect}); + }); }); for (auto& vl : viewLayers) vl->drawInvalidRects (); @@ -681,32 +671,28 @@ void Win32Frame::paint (HWND hwnd) } else { - if (deviceContext == nullptr) - deviceContext = createDrawContext (hwnd, hdc, frameSize); - if (deviceContext) + if (!legacyDrawDevice) + legacyDrawDevice = + getPlatformFactory ().asWin32Factory ()->createGraphicsDeviceContext (hwnd); + if (legacyDrawDevice) { GetRgnBox (rgn, &ps.rcPaint); CRect updateRect ((CCoord)ps.rcPaint.left, (CCoord)ps.rcPaint.top, (CCoord)ps.rcPaint.right, (CCoord)ps.rcPaint.bottom); - deviceContext->setClipRect (updateRect); - CDrawContext* drawContext = backBuffer ? backBuffer : deviceContext; - drawContext->beginDraw (); + legacyDrawDevice->setClipRect (updateRect); + + legacyDrawDevice->beginDraw (); iterateRegion (rgn, [&] (const auto& rect) { - drawContext->clearRect (rect); - getFrame ()->platformDrawRect (drawContext, rect); + legacyDrawDevice->saveGlobalState (); + legacyDrawDevice->setClipRect (updateRect); + legacyDrawDevice->clearRect (rect); + getFrame ()->platformDrawRects (legacyDrawDevice, 1, {1, rect}); + legacyDrawDevice->restoreGlobalState (); }); - drawContext->endDraw (); - if (backBuffer) - { - deviceContext->beginDraw (); - deviceContext->clearRect (updateRect); - backBuffer->copyFrom (deviceContext, updateRect, - CPoint (updateRect.left, updateRect.top)); - deviceContext->endDraw (); - } + legacyDrawDevice->endDraw (); } } } @@ -830,7 +816,7 @@ LONG_PTR WINAPI Win32Frame::proc (HWND hwnd, UINT message, WPARAM wParam, LPARAM event.mousePosition (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam)); pFrame->platformOnEvent (event); - if (event.consumed && getPlatformWindow ()) + if (event.consumed && !event.ignoreFollowUpMoveAndUpEvents () && getPlatformWindow ()) SetCapture (getPlatformWindow ()); return 0; } diff --git a/vstgui/lib/platform/win32/win32frame.h b/vstgui/lib/platform/win32/win32frame.h index 80ba85413..262b70d79 100644 --- a/vstgui/lib/platform/win32/win32frame.h +++ b/vstgui/lib/platform/win32/win32frame.h @@ -80,10 +80,9 @@ class Win32Frame final : public IPlatformFrame, public IWin32PlatformFrame HWND tooltipWindow; HWND oldFocusWindow; - SharedPointer backBuffer; - CDrawContext* deviceContext; std::unique_ptr genericOptionMenuTheme; DirectComposition::VisualPtr directCompositionVisual; + PlatformGraphicsDeviceContextPtr legacyDrawDevice; Optional currentEvent; ViewLayers viewLayers; diff --git a/vstgui/lib/platform/win32/win32support.cpp b/vstgui/lib/platform/win32/win32support.cpp index 1882ae28c..f101a49a7 100644 --- a/vstgui/lib/platform/win32/win32support.cpp +++ b/vstgui/lib/platform/win32/win32support.cpp @@ -12,7 +12,6 @@ #include "win32factory.h" #include -#include "direct2d/d2ddrawcontext.h" namespace VSTGUI { @@ -48,18 +47,6 @@ void useD2D () {} //----------------------------------------------------------------------------- void unuseD2D () {} -//----------------------------------------------------------------------------- -CDrawContext* createDrawContext (HWND window, HDC device, const CRect& surfaceRect) -{ - auto context = new D2DDrawContext (window, surfaceRect); - if (!context->usable ()) - { - context->forget (); - return nullptr; - } - return context; -} - //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- VirtualKey translateWinVirtualKey (WPARAM winVKey) diff --git a/vstgui/lib/platform/win32/win32support.h b/vstgui/lib/platform/win32/win32support.h index ee4d5cf3a..8dae44efd 100644 --- a/vstgui/lib/platform/win32/win32support.h +++ b/vstgui/lib/platform/win32/win32support.h @@ -40,7 +40,6 @@ extern IWICImagingFactory* getWICImageingFactory (); extern void useD2D (); extern void unuseD2D (); extern IDWriteFactory* getDWriteFactory (); -extern CDrawContext* createDrawContext (HWND window, HDC device, const CRect& surfaceRect); extern VirtualKey translateWinVirtualKey (WPARAM winVKey); extern void updateModifiers (Modifiers& modifiers); extern Optional keyMessageToKeyboardEvent (WPARAM wParam, LPARAM lParam); diff --git a/vstgui/lib/platform/win32/win32viewlayer.cpp b/vstgui/lib/platform/win32/win32viewlayer.cpp index ce30590d7..d3c223e54 100644 --- a/vstgui/lib/platform/win32/win32viewlayer.cpp +++ b/vstgui/lib/platform/win32/win32viewlayer.cpp @@ -5,7 +5,8 @@ #include "win32viewlayer.h" #include "win32directcomposition.h" #include "win32factory.h" -#include "direct2d/d2ddrawcontext.h" +#include "direct2d/d2dgraphicscontext.h" +#include "direct2d/d2d.h" //------------------------------------------------------------------------ namespace VSTGUI { @@ -46,17 +47,19 @@ bool Win32ViewLayer::drawInvalidRects () visual->update (r, [&] (auto deviceContext, auto updateRect, auto offsetX, auto offsetY) { COM::Ptr device; deviceContext->GetDevice (device.adoptPtr ()); - D2DDrawContext drawContext (deviceContext, viewSize, device.get ()); - drawContext.setClipRect (updateRect); + CGraphicsTransform tm; tm.translate (offsetX - updateRect.left, offsetY - updateRect.top); - CDrawContext::Transform transform (drawContext, tm); - { - drawContext.saveGlobalState (); - drawContext.clearRect (updateRect); - delegate->drawViewLayer (&drawContext, updateRect); - drawContext.restoreGlobalState (); - } + + const auto& graphicsDeviceFactory = static_cast ( + getPlatformFactory ().asWin32Factory ()->getGraphicsDeviceFactory ()); + auto graphicsDevice = graphicsDeviceFactory.find (device.get ()); + auto drawDevice = std::make_shared ( + *static_cast (graphicsDevice.get ()), deviceContext, tm); + + drawDevice->setClipRect (updateRect); + drawDevice->clearRect (updateRect); + delegate->drawViewLayerRects (drawDevice, 1, {1, updateRect}); }); } lastDrawTime = getPlatformFactory ().getTicks (); @@ -110,9 +113,6 @@ void Win32ViewLayer::setAlpha (float alpha) visual->commit (); } -//------------------------------------------------------------------------ -void Win32ViewLayer::draw (CDrawContext* context, const CRect& updateRect) {} - //------------------------------------------------------------------------ void Win32ViewLayer::onScaleFactorChanged (double newScaleFactor) {} diff --git a/vstgui/lib/platform/win32/win32viewlayer.h b/vstgui/lib/platform/win32/win32viewlayer.h index 66a9d09c6..6f335d9d9 100644 --- a/vstgui/lib/platform/win32/win32viewlayer.h +++ b/vstgui/lib/platform/win32/win32viewlayer.h @@ -28,7 +28,6 @@ class Win32ViewLayer void setSize (const CRect& size) override; void setZIndex (uint32_t zIndex) override; void setAlpha (float alpha) override; - void draw (CDrawContext* context, const CRect& updateRect) override; void onScaleFactorChanged (double newScaleFactor) override; bool drawInvalidRects (); diff --git a/vstgui/lib/vstguibase.h b/vstgui/lib/vstguibase.h index 882660aa2..bf3836a73 100644 --- a/vstgui/lib/vstguibase.h +++ b/vstgui/lib/vstguibase.h @@ -12,8 +12,8 @@ // VSTGUI Version //----------------------------------------------------------------------------- #define VSTGUI_VERSION_MAJOR 4 -#define VSTGUI_VERSION_MINOR 12 -#define VSTGUI_VERSION_PATCHLEVEL 3 +#define VSTGUI_VERSION_MINOR 13 +#define VSTGUI_VERSION_PATCHLEVEL 0 //----------------------------------------------------------------------------- // Platform definitions @@ -544,6 +544,7 @@ struct BitScopeToggleT #define VSTGUI_NEWER_THAN_4_10 VSTGUI_NEWER_THAN (4, 10) #define VSTGUI_NEWER_THAN_4_11 VSTGUI_NEWER_THAN (4, 11) +#define VSTGUI_NEWER_THAN_4_12 VSTGUI_NEWER_THAN (4, 12) } // VSTGUI diff --git a/vstgui/lib/vstguifwd.h b/vstgui/lib/vstguifwd.h index fe1296878..f68c25968 100644 --- a/vstgui/lib/vstguifwd.h +++ b/vstgui/lib/vstguifwd.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace VSTGUI { @@ -93,6 +94,18 @@ enum DragResult { kDragError = -1 }; +//----------- +// @brief Text Face +//----------- +enum CTxtFace +{ + kNormalFace = 0, + kBoldFace = 1 << 1, + kItalicFace = 1 << 2, + kUnderlineFace = 1 << 3, + kStrikethroughFace = 1 << 4 +}; + //---------------------------- // @brief Bitmap Interpolation //---------------------------- @@ -141,6 +154,10 @@ struct CMultiFrameBitmapDescription; using GradientColorStop = std::pair; using GradientColorStopMap = std::multimap; +using LinePair = std::pair; +using LineList = std::vector; +using PointList = std::vector; + // interfaces class IViewListener; class IViewEventListener; @@ -322,6 +339,10 @@ class IPlatformFrameConfig; class IPlatformFrameCallback; class IPlatformTimerCallback; class IPlatformFileSelector; +class IPlatformGraphicsDeviceFactory; +class IPlatformGraphicsDevice; +class IPlatformGraphicsDeviceContext; +class IPlatformGraphicsDeviceContextBitmapExt; struct PlatformFileExtension; struct PlatformFileSelectorConfig; @@ -342,5 +363,7 @@ using PlatformGradientPtr = std::unique_ptr; using PlatformGraphicsPathPtr = std::unique_ptr; using PlatformGraphicsPathFactoryPtr = std::shared_ptr; using PlatformFileSelectorPtr = std::shared_ptr; +using PlatformGraphicsDevicePtr = std::shared_ptr; +using PlatformGraphicsDeviceContextPtr = std::shared_ptr; } // VSTGUI diff --git a/vstgui/standalone/examples/mandelbrot/source/mandelbrotwindow.cpp b/vstgui/standalone/examples/mandelbrot/source/mandelbrotwindow.cpp index 9c50f7893..9d97db952 100644 --- a/vstgui/standalone/examples/mandelbrot/source/mandelbrotwindow.cpp +++ b/vstgui/standalone/examples/mandelbrot/source/mandelbrotwindow.cpp @@ -232,7 +232,7 @@ struct ViewController : DelegationController, { if (auto frame = view->getFrame ()) { - frame->registerScaleFactorChangedListeneer (this); + frame->registerScaleFactorChangedListener (this); scaleFactor = frame->getScaleFactor (); updateMandelbrot (); } @@ -241,7 +241,7 @@ struct ViewController : DelegationController, { if (auto frame = view->getFrame ()) { - frame->unregisterScaleFactorChangedListeneer (this); + frame->unregisterScaleFactorChangedListener (this); } } void viewWillDelete (CView* view) override diff --git a/vstgui/standalone/examples/standalone/CMakeLists.txt b/vstgui/standalone/examples/standalone/CMakeLists.txt index c9a84974c..d6ca95f54 100644 --- a/vstgui/standalone/examples/standalone/CMakeLists.txt +++ b/vstgui/standalone/examples/standalone/CMakeLists.txt @@ -12,6 +12,8 @@ set(standalone_sources "source/testappdelegate.h" "source/testmodel.cpp" "source/testmodel.h" + "../../../contrib/datepicker.h" + "../../../contrib/evbutton.h" ) set(standalone_resources @@ -20,12 +22,40 @@ set(standalone_resources "resource/resources.uidesc" "resource/test.uidesc" "resource/testpopup.uidesc" + "resource/metalwindow.uidesc" + "resource/direct3dwindow.uidesc" ) set(standalone_font "resource/font/EffectsEighty.ttf" ) +######################################################################################### +if(CMAKE_HOST_APPLE) + set(standalone_sources + ${standalone_sources} + "source/metalwindow.mm" + "source/metalwindow.h" + "source/metaltypes.h" + "source/metalshader.h" + "../../../contrib/externalview_nsview.h" + "../../../contrib/externalview_metal.h" + "../../../contrib/datepicker.mm" + "../../../contrib/evbutton_macos.mm" + ) +elseif(MSVC) + set(standalone_sources + ${standalone_sources} + "../../../contrib/externalview_hwnd.h" + "../../../contrib/externalview_direct3d12.h" + "../../../contrib/datepicker_win32.cpp" + "../../../contrib/evbutton_win32.cpp" + "source/direct3dwindow.cpp" + "source/direct3dwindow.h" + "source/direct3dshader.h" + ) +endif() + ########################################################################################## vstgui_add_executable(${target} "${standalone_sources}") vstgui_add_resources(${target} "${standalone_resources}") @@ -36,3 +66,9 @@ vstgui_set_target_rcfile(${target} "resource/standalone.rc") vstgui_set_cxx_version(${target} 17) target_include_directories(${target} PRIVATE ../../../../) set_target_properties(${target} PROPERTIES ${APP_PROPERTIES} ${VSTGUI_STANDALONE_EXAMPLES_FOLDER}) + +if(CMAKE_HOST_APPLE) + target_link_libraries(${target} PRIVATE + "-framework Metal" + ) +endif() diff --git a/vstgui/standalone/examples/standalone/resource/box.png b/vstgui/standalone/examples/standalone/resource/box.png new file mode 100644 index 000000000..023e69b80 Binary files /dev/null and b/vstgui/standalone/examples/standalone/resource/box.png differ diff --git a/vstgui/standalone/examples/standalone/resource/box_2.0x.png b/vstgui/standalone/examples/standalone/resource/box_2.0x.png new file mode 100644 index 000000000..1b91c30c6 Binary files /dev/null and b/vstgui/standalone/examples/standalone/resource/box_2.0x.png differ diff --git a/vstgui/standalone/examples/standalone/resource/direct3dwindow.uidesc b/vstgui/standalone/examples/standalone/resource/direct3dwindow.uidesc new file mode 100644 index 000000000..e86bd4c04 --- /dev/null +++ b/vstgui/standalone/examples/standalone/resource/direct3dwindow.uidesc @@ -0,0 +1,67 @@ +{ + "vstgui-ui-description": { + "version": "1", + "control-tags": {}, + "custom": { + "UIDescFilePath": { + "path": "C:\\Users\\AScheffler\\source\\git\\vstgui\\vstgui\\standalone\\examples\\standalone\\resource\\direct3dwindow.uidesc" + }, + "UIGridController": { + "Grids": "1x 1,5x 5,10x 10,12x 12,15x 15" + }, + "UIEditController": { + "EditViewScale": "1", + "EditorSize": "0, 0, 1108, 904", + "SplitViewSize_0_0": "0.7369020501138952017328165311482734978199", + "SplitViewSize_0_1": "0.2403189066059225609262028910961817018688", + "SplitViewSize_1_0": "0.4851936218678815637694867746176896616817", + "SplitViewSize_1_1": "0.5091116173120728838341619848506525158882", + "SplitViewSize_2_0": "0.6543321299638988897129365795990452170372", + "SplitViewSize_2_1": "0.3411552346570397298286536624800646677613", + "TabSwitchValue": "0", + "Version": "1" + }, + "UITemplateController": { + "SelectedTemplate": "view" + }, + "UIAttributesController": {}, + "UIViewCreatorDataSource": { + "SelectedRow": "29" + } + }, + "templates": { + "view": { + "attributes": { + "autosize": "left right top bottom ", + "background-color": "~ BlackCColor", + "background-color-draw-style": "filled and stroked", + "class": "CViewContainer", + "maxSize": "32767, 32767", + "minSize": "300, 300", + "mouse-enabled": "true", + "opacity": "1", + "origin": "0, 0", + "size": "300, 300", + "transparent": "false", + "wants-focus": "false" + }, + "children": { + "CView": { + "attributes": { + "autosize": "left right top bottom ", + "class": "CView", + "custom-view-name": "Direct3DView", + "mouse-enabled": "true", + "opacity": "1", + "origin": "20, 20", + "size": "260, 260", + "sub-controller": "Direct3DController", + "transparent": "false", + "wants-focus": "false" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/vstgui/standalone/examples/standalone/resource/metalwindow.uidesc b/vstgui/standalone/examples/standalone/resource/metalwindow.uidesc new file mode 100644 index 000000000..d7eefbc47 --- /dev/null +++ b/vstgui/standalone/examples/standalone/resource/metalwindow.uidesc @@ -0,0 +1,68 @@ +{ + "vstgui-ui-description": { + "version": "1", + "control-tags": {}, + "custom": { + "UIDescFilePath": { + "path": "/Volumes/Quasar/vstgui/vstgui/standalone/examples/standalone/resource/metalwindow.uidesc" + }, + "UIGridController": { + "Grids": "1x 1,5x 5,10x 10,12x 12,15x 15" + }, + "UIEditController": { + "EditViewScale": "1", + "EditorSize": "0, 0, 810, 808", + "SplitViewSize_0_0": "0.8337595907928389005192570948565844446421", + "SplitViewSize_0_1": "0.140664961636828650393127304596418980509", + "SplitViewSize_1_0": "0.4833759590792838789496954632340930402279", + "SplitViewSize_1_1": "0.5102301790281329463283555014641024172306", + "SplitViewSize_2_0": "0.7777777777777777901135891625017393380404", + "SplitViewSize_2_1": "0.2160493827160493707228994253455311991274", + "TabSwitchValue": "0", + "UI Theme": "Dark", + "Version": "1", + "ViewBackground": "3" + }, + "UITemplateController": { + "SelectedTemplate": "view" + }, + "UIAttributesController": {}, + "UIViewCreatorDataSource": { + "SelectedRow": "29" + } + }, + "templates": { + "view": { + "attributes": { + "autosize": "left right top bottom ", + "background-color": "~ BlackCColor", + "background-color-draw-style": "filled and stroked", + "class": "CViewContainer", + "minSize": "300, 300", + "mouse-enabled": "true", + "opacity": "1", + "origin": "0, 0", + "size": "300, 300", + "transparent": "false", + "wants-focus": "false" + }, + "children": { + "CView": { + "attributes": { + "autosize": "left right top bottom ", + "class": "CView", + "custom-view-name": "MetalView", + "mouse-enabled": "true", + "opacity": "1", + "origin": "20, 20", + "size": "260, 260", + "sub-controller": "MetalController", + "transparent": "false", + "wants-focus": "false" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/vstgui/standalone/examples/standalone/resource/resources.uidesc b/vstgui/standalone/examples/standalone/resource/resources.uidesc index 140fd848e..1618e1058 100644 --- a/vstgui/standalone/examples/standalone/resource/resources.uidesc +++ b/vstgui/standalone/examples/standalone/resource/resources.uidesc @@ -12,6 +12,22 @@ "data": "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" } }, + "box": { + "nineparttiled-offsets": "40, 40, 25, 25", + "path": "box.png", + "data": { + "encoding": "base64", + "data": "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" + } + }, + "box_2.0x": { + "path": "box_2.0x.png", + "scale-factor": "2", + "data": { + "encoding": "base64", + "data": "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" + } + }, "knob": { "mulitframe-frames-per-row": "1", "multiframe-num-frames": "6", diff --git a/vstgui/standalone/examples/standalone/resource/test.uidesc b/vstgui/standalone/examples/standalone/resource/test.uidesc index e8de96570..e31b0a38c 100644 --- a/vstgui/standalone/examples/standalone/resource/test.uidesc +++ b/vstgui/standalone/examples/standalone/resource/test.uidesc @@ -23,25 +23,25 @@ "SelectedTemplate": "view" }, "UIEditController": { - "EditViewScale": "1.5", - "EditorSize": "0, 0, 1158, 997", - "SplitViewSize_0_0": "0.7415036045314109536263913469156250357628", - "SplitViewSize_0_1": "0.2378990731204943254173400646322988905013", - "SplitViewSize_1_0": "0.5849639546858907968029939183907117694616", - "SplitViewSize_1_1": "0.4109165808444902312501767482899595052004", - "SplitViewSize_2_0": "0.6424870466321243034357735268713440746069", - "SplitViewSize_2_1": "0.3531951640759931065893795221199980005622", - "TabSwitchValue": "2", + "EditViewScale": "1", + "EditorSize": "0, 0, 923, 779", + "SplitViewSize_0_0": "0.8034528552456838834672225857502780854702", + "SplitViewSize_0_1": "0.1699867197875165880649461769280605949461", + "SplitViewSize_1_0": "0.6108897742363877947369132925814483314753", + "SplitViewSize_1_1": "0.3864541832669322607429762683750595897436", + "SplitViewSize_2_0": "0.5796316359696641606902289822755847126245", + "SplitViewSize_2_1": "0.4149512459371614281344875507784308865666", + "TabSwitchValue": "0", "UI Theme": "Dark", "Version": "1", "ViewBackground": "3" }, "UIAttributesController": {}, "UIViewCreatorDataSource": { - "SelectedRow": "24" + "SelectedRow": "4" }, "UIDescFilePath": { - "path": "/Volumes/vst3/vstgui/vstgui/standalone/examples/standalone/resource/test.uidesc" + "path": "/Users/scheffle/git/vstgui/vstgui/standalone/examples/standalone/resource/test.uidesc" }, "UITagsDataSource": { "SelectedRow": "-1" @@ -71,12 +71,12 @@ "background-color": "~ BlackCColor", "background-color-draw-style": "filled and stroked", "class": "CViewContainer", - "maxSize": "800, 800", - "minSize": "410, 350", + "maxSize": "800, 1000", + "minSize": "410, 515", "mouse-enabled": "true", "opacity": "1", "origin": "0, 0", - "size": "410, 350", + "size": "410, 515", "sub-controller": "DisabledControlsController", "transparent": "true", "wants-focus": "false" @@ -104,7 +104,7 @@ "class": "CViewContainer", "mouse-enabled": "true", "opacity": "1", - "origin": "0, 330", + "origin": "0, 495", "size": "410, 20", "transparent": "false", "wants-focus": "false" @@ -126,7 +126,7 @@ "radial-center": "0.5, 0.5", "radial-radius": "1", "round-rect-radius": "5", - "size": "410, 350", + "size": "410, 515", "transparent": "false", "wants-focus": "false" } @@ -190,6 +190,79 @@ "zoom-factor": "10" } }, + "CStringListControl": { + "attributes": { + "back-color": "~ WhiteCColor", + "back-color-selected": "TextShadow", + "class": "CStringListControl", + "control-tag": "Weekdays", + "default-value": "0.5", + "font": "~ NormalFont", + "font-color": "~ BlackCColor", + "font-color-selected": "~ BlackCColor", + "hover-color": "Hover", + "line-color": "~ BlackCColor", + "line-width": "-1", + "max-value": "7", + "min-value": "0", + "mouse-enabled": "true", + "opacity": "1", + "origin": "15, 200", + "row-height": "16", + "size": "110, 144", + "style-hover": "true", + "sub-controller": "WeekdaysController", + "text-alignment": "center", + "text-inset": "5", + "tooltip": "String List Control", + "transparent": "false", + "wants-focus": "true", + "wheel-inc-value": "0.1" + } + }, + "CTextEdit": { + "attributes": { + "autosize": "left right top ", + "back-color": "~ BlackCColor", + "background-offset": "0, 0", + "class": "CTextEdit", + "control-tag": "MutableString", + "default-value": "0.5", + "font": "~ NormalFont", + "font-antialias": "true", + "font-color": "~ WhiteCColor", + "frame-color": "~ WhiteCColor", + "frame-width": "1", + "immediate-text-change": "true", + "max-value": "1", + "min-value": "0", + "mouse-enabled": "true", + "opacity": "1", + "origin": "40, 50", + "round-rect-radius": "3", + "secure-style": "false", + "shadow-color": "~ RedCColor", + "size": "260, 20", + "style-3D-in": "false", + "style-3D-out": "false", + "style-doubleclick": "false", + "style-no-draw": "false", + "style-no-frame": "false", + "style-no-text": "false", + "style-round-rect": "true", + "style-shadow-text": "false", + "text-alignment": "center", + "text-inset": "0, 0", + "text-rotation": "0", + "text-shadow-offset": "1, 1", + "title": "This is a string value", + "tooltip": "Text Edit", + "transparent": "false", + "value-precision": "2", + "wants-focus": "true", + "wheel-inc-value": "0.1" + } + }, "CSlider": { "attributes": { "autosize": "left right top ", @@ -637,6 +710,7 @@ "autosize": "left right top bottom ", "back-color": "~ WhiteCColor", "background-offset": "0, 0", + "bitmap": "box", "class": "CMultiLineTextLabel", "default-value": "0.5", "font": "EffectsEighty", @@ -652,7 +726,7 @@ "origin": "135, 290", "round-rect-radius": "6", "shadow-color": "TextShadow", - "size": "265, 50", + "size": "130, 215", "style-3D-in": "false", "style-3D-out": "false", "style-no-draw": "false", @@ -661,7 +735,7 @@ "style-round-rect": "false", "style-shadow-text": "false", "text-alignment": "left", - "text-inset": "3, 3", + "text-inset": "13, 13", "text-rotation": "0", "text-shadow-offset": "1, 1", "title": "This is a multine line text label! That wraps the lines if they are too long.", @@ -815,7 +889,7 @@ "min-value": "0", "mouse-enabled": "true", "opacity": "1", - "origin": "265, 225", + "origin": "270, 225", "size": "70, 70", "skip-handle-drawing": "true", "tooltip": "Knob", @@ -826,49 +900,6 @@ "zoom-factor": "1.5" } }, - "CTextEdit": { - "attributes": { - "autosize": "left right top ", - "back-color": "~ BlackCColor", - "background-offset": "0, 0", - "class": "CTextEdit", - "control-tag": "MutableString", - "default-value": "0.5", - "font": "~ NormalFont", - "font-antialias": "true", - "font-color": "~ WhiteCColor", - "frame-color": "~ WhiteCColor", - "frame-width": "1", - "immediate-text-change": "true", - "max-value": "1", - "min-value": "0", - "mouse-enabled": "true", - "opacity": "1", - "origin": "40, 50", - "round-rect-radius": "3", - "secure-style": "false", - "shadow-color": "~ RedCColor", - "size": "260, 20", - "style-3D-in": "false", - "style-3D-out": "false", - "style-doubleclick": "false", - "style-no-draw": "false", - "style-no-frame": "false", - "style-no-text": "false", - "style-round-rect": "true", - "style-shadow-text": "false", - "text-alignment": "center", - "text-inset": "0, 0", - "text-rotation": "0", - "text-shadow-offset": "1, 1", - "title": "This is a string value", - "tooltip": "Text Edit", - "transparent": "false", - "value-precision": "2", - "wants-focus": "true", - "wheel-inc-value": "0.1" - } - }, "CParamDisplay": { "attributes": { "autosize": "left right top ", @@ -908,52 +939,115 @@ "wheel-inc-value": "0.1" } }, - "CStringListControl": { - "attributes": { - "back-color": "~ WhiteCColor", - "back-color-selected": "TextShadow", - "class": "CStringListControl", - "control-tag": "Weekdays", - "default-value": "0.5", - "font": "~ NormalFont", - "font-color": "~ BlackCColor", - "font-color-selected": "~ BlackCColor", - "hover-color": "Hover", - "line-color": "~ BlackCColor", - "line-width": "-1", - "max-value": "7", - "min-value": "0", - "mouse-enabled": "true", - "opacity": "1", - "origin": "15, 200", - "row-height": "16", - "size": "110, 144", - "style-hover": "true", - "sub-controller": "WeekdaysController", - "text-alignment": "center", - "text-inset": "5", - "tooltip": "String List Control", - "transparent": "false", - "wants-focus": "true", - "wheel-inc-value": "0.1" - } - }, - "CAutoAnimation": { + "CScrollView": { "attributes": { - "animation-time": "32", - "bitmap": "autoanimation", - "bitmap-offset": "5, 5", - "class": "CAutoAnimation", - "default-value": "0.5", - "max-value": "36", - "min-value": "0", + "auto-drag-scrolling": "false", + "auto-hide-scrollbars": "false", + "autosize": "left top bottom ", + "background-color": "~ BlackCColor", + "background-color-draw-style": "filled and stroked", + "bordered": "true", + "class": "CScrollView", + "container-size": "400, 800", + "follow-focus-view": "false", + "horizontal-scrollbar": "true", "mouse-enabled": "true", "opacity": "1", - "origin": "85, 5", - "size": "30, 30", - "transparent": "false", - "wants-focus": "false", - "wheel-inc-value": "0.1" + "origin": "15, 355", + "overlay-scrollbars": "false", + "scrollbar-background-color": "#ffffffc8", + "scrollbar-frame-color": "~ BlackCColor", + "scrollbar-scroller-color": "~ BlueCColor", + "scrollbar-width": "12", + "size": "110, 150", + "transparent": "true", + "vertical-scrollbar": "true", + "wants-focus": "false" + }, + "children": { + "CView": { + "attributes": { + "autosize": "left top ", + "class": "CView", + "custom-view-name": "DatePicker", + "mouse-enabled": "true", + "opacity": "1", + "origin": "5, 10", + "size": "110, 25", + "sub-controller": "DatePickerController", + "transparent": "false", + "wants-focus": "true" + } + }, + "CControl": { + "attributes": { + "class": "CControl", + "control-tag": "Activate", + "custom-view-name": "Native Checkbox", + "default-value": "0.5", + "max-value": "1", + "min-value": "0", + "mouse-enabled": "true", + "opacity": "1", + "origin": "5, 40", + "size": "110, 20", + "sub-controller": "DatePickerController", + "transparent": "false", + "wants-focus": "true", + "wheel-inc-value": "0.1" + } + }, + "CControl": { + "attributes": { + "class": "CControl", + "custom-view-name": "Native Push Button", + "default-value": "0.5", + "max-value": "1", + "min-value": "0", + "mouse-enabled": "true", + "opacity": "1", + "origin": "0, 65", + "size": "100, 24", + "sub-controller": "DatePickerController", + "transparent": "false", + "wants-focus": "true", + "wheel-inc-value": "0.1" + } + }, + "CControl": { + "attributes": { + "class": "CControl", + "custom-view-name": "Native OnOff Button", + "default-value": "0.5", + "max-value": "1", + "min-value": "0", + "mouse-enabled": "true", + "opacity": "1", + "origin": "0, 90", + "size": "100, 25", + "sub-controller": "DatePickerController", + "transparent": "false", + "wants-focus": "true", + "wheel-inc-value": "0.1" + } + }, + "CControl": { + "attributes": { + "class": "CControl", + "custom-view-name": "Native Radio Button", + "default-value": "0.5", + "max-value": "1", + "min-value": "0", + "mouse-enabled": "true", + "opacity": "1", + "origin": "5, 120", + "size": "100, 20", + "sub-controller": "DatePickerController", + "transparent": "false", + "wants-focus": "true", + "wheel-inc-value": "0.1" + } + } } } } diff --git a/vstgui/standalone/examples/standalone/source/direct3dshader.h b/vstgui/standalone/examples/standalone/source/direct3dshader.h new file mode 100644 index 000000000..8b9327a4b --- /dev/null +++ b/vstgui/standalone/examples/standalone/source/direct3dshader.h @@ -0,0 +1,37 @@ +#pragma once + +static constexpr auto shaderCode = R"( + +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +struct PSInput +{ + float4 position : SV_POSITION; + float4 color : COLOR; +}; + +PSInput VSMain(float4 position : POSITION, float4 color : COLOR) +{ + PSInput result; + + result.position = position; + result.color = color; + + return result; +} + +float4 PSMain(PSInput input) : SV_TARGET +{ + return input.color; +} + +)"; diff --git a/vstgui/standalone/examples/standalone/source/direct3dwindow.cpp b/vstgui/standalone/examples/standalone/source/direct3dwindow.cpp new file mode 100644 index 000000000..34d5b4600 --- /dev/null +++ b/vstgui/standalone/examples/standalone/source/direct3dwindow.cpp @@ -0,0 +1,697 @@ +// 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 "direct3dwindow.h" +#include "direct3dshader.h" +#include "vstgui/contrib/externalview_direct3d12.h" +#include "vstgui/lib/cexternalview.h" +#include "vstgui/lib/cvstguitimer.h" +#include "vstgui/lib/platform/win32/win32factory.h" +#include "vstgui/standalone/include/helpers/uidesc/customization.h" +#include "vstgui/standalone/include/helpers/windowcontroller.h" +#include "vstgui/standalone/include/iuidescwindow.h" +#include "vstgui/uidescription/delegationcontroller.h" +#include "vstgui/uidescription/iuidescription.h" +#include "vstgui/uidescription/uiattributes.h" + +#include +#include + +#include + +#define VSTGUI_USE_THREADED_DIRECT3D12_EXAMPLE 1 + +#ifdef _MSC_VER +#pragma comment(lib, "d3dcompiler.lib") +#endif + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Standalone { + +using Microsoft::WRL::ComPtr; + +struct CD3DX12_DEFAULT +{ +}; +extern const DECLSPEC_SELECTANY CD3DX12_DEFAULT D3D12_DEFAULT; + +inline UINT8 D3D12GetFormatPlaneCount (_In_ ID3D12Device* pDevice, DXGI_FORMAT Format) noexcept +{ + D3D12_FEATURE_DATA_FORMAT_INFO formatInfo = {Format, 0}; + if (FAILED (pDevice->CheckFeatureSupport (D3D12_FEATURE_FORMAT_INFO, &formatInfo, + sizeof (formatInfo)))) + { + return 0; + } + return formatInfo.PlaneCount; +} + +//------------------------------------------------------------------------------------------------ +inline constexpr UINT D3D12CalcSubresource (UINT MipSlice, UINT ArraySlice, UINT PlaneSlice, + UINT MipLevels, UINT ArraySize) noexcept +{ + return MipSlice + ArraySlice * MipLevels + PlaneSlice * MipLevels * ArraySize; +} + +struct CD3DX12_HEAP_PROPERTIES : public D3D12_HEAP_PROPERTIES +{ + CD3DX12_HEAP_PROPERTIES () = default; + explicit CD3DX12_HEAP_PROPERTIES (const D3D12_HEAP_PROPERTIES& o) noexcept + : D3D12_HEAP_PROPERTIES (o) + { + } + CD3DX12_HEAP_PROPERTIES (D3D12_CPU_PAGE_PROPERTY cpuPageProperty, + D3D12_MEMORY_POOL memoryPoolPreference, UINT creationNodeMask = 1, + UINT nodeMask = 1) + noexcept + { + Type = D3D12_HEAP_TYPE_CUSTOM; + CPUPageProperty = cpuPageProperty; + MemoryPoolPreference = memoryPoolPreference; + CreationNodeMask = creationNodeMask; + VisibleNodeMask = nodeMask; + } + explicit CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE type, UINT creationNodeMask = 1, + UINT nodeMask = 1) noexcept + { + Type = type; + CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + CreationNodeMask = creationNodeMask; + VisibleNodeMask = nodeMask; + } + bool IsCPUAccessible () const noexcept + { + return Type == D3D12_HEAP_TYPE_UPLOAD || Type == D3D12_HEAP_TYPE_READBACK || + (Type == D3D12_HEAP_TYPE_CUSTOM && + (CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE || + CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_BACK)); + } +}; + +struct CD3DX12_RESOURCE_DESC : public D3D12_RESOURCE_DESC +{ + CD3DX12_RESOURCE_DESC () = default; + explicit CD3DX12_RESOURCE_DESC (const D3D12_RESOURCE_DESC& o) noexcept : D3D12_RESOURCE_DESC (o) + { + } + CD3DX12_RESOURCE_DESC (D3D12_RESOURCE_DIMENSION dimension, UINT64 alignment, UINT64 width, + UINT height, UINT16 depthOrArraySize, UINT16 mipLevels, + DXGI_FORMAT format, UINT sampleCount, UINT sampleQuality, + D3D12_TEXTURE_LAYOUT layout, D3D12_RESOURCE_FLAGS flags) + noexcept + { + Dimension = dimension; + Alignment = alignment; + Width = width; + Height = height; + DepthOrArraySize = depthOrArraySize; + MipLevels = mipLevels; + Format = format; + SampleDesc.Count = sampleCount; + SampleDesc.Quality = sampleQuality; + Layout = layout; + Flags = flags; + } + static inline CD3DX12_RESOURCE_DESC + Buffer (const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE) noexcept + { + return CD3DX12_RESOURCE_DESC (D3D12_RESOURCE_DIMENSION_BUFFER, resAllocInfo.Alignment, + resAllocInfo.SizeInBytes, 1, 1, 1, DXGI_FORMAT_UNKNOWN, 1, 0, + D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags); + } + static inline CD3DX12_RESOURCE_DESC + Buffer (UINT64 width, D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC (D3D12_RESOURCE_DIMENSION_BUFFER, alignment, width, 1, 1, 1, + DXGI_FORMAT_UNKNOWN, 1, 0, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + flags); + } + static inline CD3DX12_RESOURCE_DESC Tex1D ( + DXGI_FORMAT format, UINT64 width, UINT16 arraySize = 1, UINT16 mipLevels = 0, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC (D3D12_RESOURCE_DIMENSION_TEXTURE1D, alignment, width, 1, + arraySize, mipLevels, format, 1, 0, layout, flags); + } + static inline CD3DX12_RESOURCE_DESC Tex2D ( + DXGI_FORMAT format, UINT64 width, UINT height, UINT16 arraySize = 1, UINT16 mipLevels = 0, + UINT sampleCount = 1, UINT sampleQuality = 0, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC (D3D12_RESOURCE_DIMENSION_TEXTURE2D, alignment, width, height, + arraySize, mipLevels, format, sampleCount, sampleQuality, + layout, flags); + } + static inline CD3DX12_RESOURCE_DESC Tex3D ( + DXGI_FORMAT format, UINT64 width, UINT height, UINT16 depth, UINT16 mipLevels = 0, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC (D3D12_RESOURCE_DIMENSION_TEXTURE3D, alignment, width, height, + depth, mipLevels, format, 1, 0, layout, flags); + } + inline UINT16 Depth () const noexcept + { + return (Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? DepthOrArraySize : 1); + } + inline UINT16 ArraySize () const noexcept + { + return (Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE3D ? DepthOrArraySize : 1); + } + inline UINT8 PlaneCount (_In_ ID3D12Device* pDevice) const noexcept + { + return D3D12GetFormatPlaneCount (pDevice, Format); + } + inline UINT Subresources (_In_ ID3D12Device* pDevice) const noexcept + { + return MipLevels * ArraySize () * PlaneCount (pDevice); + } + inline UINT CalcSubresource (UINT MipSlice, UINT ArraySlice, UINT PlaneSlice) noexcept + { + return D3D12CalcSubresource (MipSlice, ArraySlice, PlaneSlice, MipLevels, ArraySize ()); + } +}; + +struct CD3DX12_RESOURCE_BARRIER : public D3D12_RESOURCE_BARRIER +{ + CD3DX12_RESOURCE_BARRIER () = default; + explicit CD3DX12_RESOURCE_BARRIER (const D3D12_RESOURCE_BARRIER& o) noexcept + : D3D12_RESOURCE_BARRIER (o) + { + } + static inline CD3DX12_RESOURCE_BARRIER + Transition (_In_ ID3D12Resource* pResource, D3D12_RESOURCE_STATES stateBefore, + D3D12_RESOURCE_STATES stateAfter, + UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_BARRIER_FLAGS flags = D3D12_RESOURCE_BARRIER_FLAG_NONE) noexcept + { + CD3DX12_RESOURCE_BARRIER result = {}; + D3D12_RESOURCE_BARRIER& barrier = result; + result.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + result.Flags = flags; + barrier.Transition.pResource = pResource; + barrier.Transition.StateBefore = stateBefore; + barrier.Transition.StateAfter = stateAfter; + barrier.Transition.Subresource = subresource; + return result; + } + static inline CD3DX12_RESOURCE_BARRIER Aliasing (_In_ ID3D12Resource* pResourceBefore, + _In_ ID3D12Resource* pResourceAfter) noexcept + { + CD3DX12_RESOURCE_BARRIER result = {}; + D3D12_RESOURCE_BARRIER& barrier = result; + result.Type = D3D12_RESOURCE_BARRIER_TYPE_ALIASING; + barrier.Aliasing.pResourceBefore = pResourceBefore; + barrier.Aliasing.pResourceAfter = pResourceAfter; + return result; + } + static inline CD3DX12_RESOURCE_BARRIER UAV (_In_ ID3D12Resource* pResource) noexcept + { + CD3DX12_RESOURCE_BARRIER result = {}; + D3D12_RESOURCE_BARRIER& barrier = result; + result.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.UAV.pResource = pResource; + return result; + } +}; + +struct CD3DX12_CPU_DESCRIPTOR_HANDLE : public D3D12_CPU_DESCRIPTOR_HANDLE +{ + CD3DX12_CPU_DESCRIPTOR_HANDLE () = default; + explicit CD3DX12_CPU_DESCRIPTOR_HANDLE (const D3D12_CPU_DESCRIPTOR_HANDLE& o) noexcept + : D3D12_CPU_DESCRIPTOR_HANDLE (o) + { + } + CD3DX12_CPU_DESCRIPTOR_HANDLE (CD3DX12_DEFAULT) noexcept { ptr = 0; } + CD3DX12_CPU_DESCRIPTOR_HANDLE (_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other, + INT offsetScaledByIncrementSize) + noexcept + { + InitOffsetted (other, offsetScaledByIncrementSize); + } + CD3DX12_CPU_DESCRIPTOR_HANDLE (_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other, + INT offsetInDescriptors, UINT descriptorIncrementSize) + noexcept + { + InitOffsetted (other, offsetInDescriptors, descriptorIncrementSize); + } + CD3DX12_CPU_DESCRIPTOR_HANDLE& Offset (INT offsetInDescriptors, + UINT descriptorIncrementSize) noexcept + { + ptr = SIZE_T (INT64 (ptr) + INT64 (offsetInDescriptors) * INT64 (descriptorIncrementSize)); + return *this; + } + CD3DX12_CPU_DESCRIPTOR_HANDLE& Offset (INT offsetScaledByIncrementSize) noexcept + { + ptr = SIZE_T (INT64 (ptr) + INT64 (offsetScaledByIncrementSize)); + return *this; + } + bool operator== (_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other) const noexcept + { + return (ptr == other.ptr); + } + bool operator!= (_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other) const noexcept + { + return (ptr != other.ptr); + } + CD3DX12_CPU_DESCRIPTOR_HANDLE& operator= (const D3D12_CPU_DESCRIPTOR_HANDLE& other) noexcept + { + ptr = other.ptr; + return *this; + } + + inline void InitOffsetted (_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, + INT offsetScaledByIncrementSize) noexcept + { + InitOffsetted (*this, base, offsetScaledByIncrementSize); + } + + inline void InitOffsetted (_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, + INT offsetInDescriptors, UINT descriptorIncrementSize) noexcept + { + InitOffsetted (*this, base, offsetInDescriptors, descriptorIncrementSize); + } + + static inline void InitOffsetted (_Out_ D3D12_CPU_DESCRIPTOR_HANDLE& handle, + _In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, + INT offsetScaledByIncrementSize) noexcept + { + handle.ptr = SIZE_T (INT64 (base.ptr) + INT64 (offsetScaledByIncrementSize)); + } + + static inline void InitOffsetted (_Out_ D3D12_CPU_DESCRIPTOR_HANDLE& handle, + _In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, + INT offsetInDescriptors, + UINT descriptorIncrementSize) noexcept + { + handle.ptr = SIZE_T (INT64 (base.ptr) + + INT64 (offsetInDescriptors) * INT64 (descriptorIncrementSize)); + } +}; + +//------------------------------------------------------------------------ +struct ExampleRenderer : public ExternalView::IDirect3D12Renderer +{ + static constexpr uint32_t FrameCount = 2; + + ExternalView::IDirect3D12View* m_view; + + bool init (ExternalView::IDirect3D12View* view) override + { + m_view = view; + return true; + } + + void render (ID3D12CommandQueue* queue) override { doRender (queue); } + + void beforeSizeUpdate () override { freeFrameResources (); } + + void onSizeUpdate (ExternalView::IntSize newSize, double scaleFactor) override + { + m_viewport = {0.f, 0.f, static_cast (newSize.width), + static_cast (newSize.height)}; + m_scissorRect = {0, 0, static_cast (newSize.width), + static_cast (newSize.height)}; + + createFrameResources (); + m_view->render (); + } + + void onAttach () override + { + createFrameResources (); + loadAssets (); + m_view->render (); +#if VSTGUI_USE_THREADED_DIRECT3D12_EXAMPLE + stopRenderThread = false; + renderThread = std::thread ([this] () { + while (!stopRenderThread) + { + try + { + m_view->render (); + } + catch (...) + { + stopRenderThread = true; + } + std::this_thread::sleep_for (std::chrono::milliseconds (1)); + } + }); +#else + timer = makeOwned ([this] (auto) { m_view->render (); }, 16); +#endif + } + void onRemove () override + { +#if VSTGUI_USE_THREADED_DIRECT3D12_EXAMPLE + stopRenderThread = true; + if (renderThread.joinable ()) + renderThread.join (); +#else + timer = nullptr; +#endif + freeFrameResources (); + onDestroy (); + } + + uint32_t getFrameCount () const override { return FrameCount; } + + void freeFrameResources () + { + for (UINT n = 0; n < FrameCount; n++) + m_renderTargets[n].Reset (); + m_view->setFrameIndex (0); + } + + void createFrameResources () + { + // Create descriptor heaps. + { + // Describe and create a render target view (RTV) descriptor heap. + D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; + rtvHeapDesc.NumDescriptors = FrameCount; + rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + ThrowIfFailed (m_view->getDevice ()->CreateDescriptorHeap (&rtvHeapDesc, + IID_PPV_ARGS (&m_rtvHeap))); + + m_rtvDescriptorSize = m_view->getDevice ()->GetDescriptorHandleIncrementSize ( + D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + } + // Create frame resources. + { + auto rtvHandle = (m_rtvHeap->GetCPUDescriptorHandleForHeapStart ()); + + // Create a RTV for each frame. + for (UINT n = 0; n < FrameCount; n++) + { + ThrowIfFailed ( + m_view->getSwapChain ()->GetBuffer (n, IID_PPV_ARGS (&m_renderTargets[n]))); + m_view->getDevice ()->CreateRenderTargetView (m_renderTargets[n].Get (), nullptr, + rtvHandle); + rtvHandle.ptr = + SIZE_T (INT64 (rtvHandle.ptr) + INT64 (1) * INT64 (m_rtvDescriptorSize)); + } + } + } + + // Load the sample assets. + void loadAssets () + { + // Create an empty root signature. + { + D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = { + 0, nullptr, 0, nullptr, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT}; + + ComPtr signature; + ComPtr error; + ThrowIfFailed (D3D12SerializeRootSignature ( + &rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); + ThrowIfFailed (m_view->getDevice ()->CreateRootSignature ( + 0, signature->GetBufferPointer (), signature->GetBufferSize (), + IID_PPV_ARGS (&m_rootSignature))); + } + + // Create the pipeline state, which includes compiling and loading shaders. + { + ComPtr vertexShader; + ComPtr pixelShader; + +#if defined(_DEBUG) + // Enable better shader debugging with the graphics debugging tools. + UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; +#else + UINT compileFlags = 0; +#endif + + ThrowIfFailed (D3DCompile (shaderCode, strlen (shaderCode), nullptr, nullptr, nullptr, + "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, + nullptr)); + ThrowIfFailed (D3DCompile (shaderCode, strlen (shaderCode), nullptr, nullptr, nullptr, + "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr)); + + // Define the vertex input layout. + D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, + D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}}; + + // Describe and create the graphics pipeline state object (PSO). + D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; + psoDesc.InputLayout = {inputElementDescs, _countof (inputElementDescs)}; + psoDesc.pRootSignature = m_rootSignature.Get (); + psoDesc.VS = {vertexShader.Get ()->GetBufferPointer (), + vertexShader.Get ()->GetBufferSize ()}; + psoDesc.PS = {pixelShader.Get ()->GetBufferPointer (), + pixelShader.Get ()->GetBufferSize ()}; + psoDesc.RasterizerState = {D3D12_FILL_MODE_SOLID, + D3D12_CULL_MODE_BACK, + FALSE, + D3D12_DEFAULT_DEPTH_BIAS, + D3D12_DEFAULT_DEPTH_BIAS_CLAMP, + D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS, + TRUE, + FALSE, + FALSE, + 0, + D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF}; + constexpr D3D12_BLEND_DESC DefaultBlendDesc = { + FALSE, + FALSE, + {FALSE, FALSE, D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, + D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, D3D12_LOGIC_OP_NOOP, + D3D12_COLOR_WRITE_ENABLE_ALL}}; + psoDesc.BlendState = DefaultBlendDesc; + psoDesc.DepthStencilState.DepthEnable = FALSE; + psoDesc.DepthStencilState.StencilEnable = FALSE; + psoDesc.SampleMask = UINT_MAX; + psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + psoDesc.NumRenderTargets = 1; + psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; + psoDesc.SampleDesc.Count = 1; + ThrowIfFailed (m_view->getDevice ()->CreateGraphicsPipelineState ( + &psoDesc, IID_PPV_ARGS (&m_pipelineState))); + } + + // Create the command list. + ThrowIfFailed (m_view->getDevice ()->CreateCommandList ( + 0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_view->getCommandAllocator (), + m_pipelineState.Get (), IID_PPV_ARGS (&m_commandList))); + + // Command lists are created in the recording state, but there is nothing + // to record yet. The main loop expects it to be closed, so close it now. + ThrowIfFailed (m_commandList->Close ()); + + updateAndUploadVertexBuffer (); + } + + void updateAndUploadVertexBuffer () + { + // Create the vertex buffer. + + // Define the geometry for a triangle. + Vertex triangleVertices[] = {{{0.0f, 0.8f, 0.0f}, colorRight}, + {{0.8f, -0.8f, 0.0f}, colorLeft}, + {{-0.8f, -0.8f, 0.0f}, colorTop}}; + + const UINT vertexBufferSize = sizeof (triangleVertices); + + // Note: using upload heaps to transfer static data like vert buffers is not + // recommended. Every time the GPU needs it, the upload heap will be marshalled + // over. Please read up on Default Heap usage. An upload heap is used here for + // code simplicity and because there are very few verts to actually transfer. + auto heapProps = CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_UPLOAD); + auto bufferDesc = CD3DX12_RESOURCE_DESC::Buffer (vertexBufferSize); + ThrowIfFailed (m_view->getDevice ()->CreateCommittedResource ( + &heapProps, D3D12_HEAP_FLAG_NONE, &bufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, IID_PPV_ARGS (&m_vertexBuffer))); + + // Copy the triangle data to the vertex buffer. + UINT8* pVertexDataBegin; + D3D12_RANGE readRange {0, 0}; // We do not intend to read from this resource on the CPU. + ThrowIfFailed ( + m_vertexBuffer->Map (0, &readRange, reinterpret_cast (&pVertexDataBegin))); + memcpy (pVertexDataBegin, triangleVertices, sizeof (triangleVertices)); + m_vertexBuffer->Unmap (0, nullptr); + + // Initialize the vertex buffer view. + m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress (); + m_vertexBufferView.StrideInBytes = sizeof (Vertex); + m_vertexBufferView.SizeInBytes = vertexBufferSize; + } + + void updateColors () + { + ++frameCounter; + colorTop.x = (1.f + std::sin (frameCounter * 0.013f)) * 0.5f; + colorTop.y = (1.f + std::sin (frameCounter * 0.021f)) * 0.5f; + colorTop.z = (1.f + std::sin (frameCounter * 0.037f)) * 0.5f; + + colorLeft.x = (1.f + std::sin (frameCounter * 0.031f)) * 0.5f; + colorLeft.y = (1.f + std::sin (frameCounter * 0.021f)) * 0.5f; + colorLeft.z = (1.f + std::sin (frameCounter * 0.011f)) * 0.5f; + + colorRight.x = (1.f + std::sin (frameCounter * 0.025f)) * 0.5f; + colorRight.y = (1.f + std::sin (frameCounter * 0.012f)) * 0.5f; + colorRight.z = (1.f + std::sin (frameCounter * 0.031f)) * 0.5f; + } + + void doRender (ID3D12CommandQueue* queue) + { + updateColors (); + updateAndUploadVertexBuffer (); + + // Record all the commands we need to render the scene into the command list. + populateCommandList (); + + // Execute the command list. + ID3D12CommandList* ppCommandLists[] = {m_commandList.Get ()}; + queue->ExecuteCommandLists (_countof (ppCommandLists), ppCommandLists); + } + + void onDestroy () + { + m_vertexBuffer.Reset (); + + for (auto i = 0; i < FrameCount; ++i) + m_renderTargets[i].Reset (); + m_rootSignature.Reset (); + m_rtvHeap.Reset (); + m_pipelineState.Reset (); + m_commandList.Reset (); + } + + void populateCommandList () + { + // Command list allocators can only be reset when the associated + // command lists have finished execution on the GPU; apps should use + // fences to determine GPU execution progress. + ThrowIfFailed (m_view->getCommandAllocator ()->Reset ()); + + // However, when ExecuteCommandList() is called on a particular command + // list, that command list can then be reset at any time and must be before + // re-recording. + ThrowIfFailed ( + m_commandList->Reset (m_view->getCommandAllocator (), m_pipelineState.Get ())); + + // Set necessary state. + m_commandList->SetGraphicsRootSignature (m_rootSignature.Get ()); + m_commandList->RSSetViewports (1, &m_viewport); + m_commandList->RSSetScissorRects (1, &m_scissorRect); + + // Indicate that the back buffer will be used as a render target. + auto transition = CD3DX12_RESOURCE_BARRIER::Transition ( + m_renderTargets[m_view->getFrameIndex ()].Get (), D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATE_RENDER_TARGET); + m_commandList->ResourceBarrier (1, &transition); + + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle (m_rtvHeap->GetCPUDescriptorHandleForHeapStart (), + m_view->getFrameIndex (), m_rtvDescriptorSize); + m_commandList->OMSetRenderTargets (1, &rtvHandle, FALSE, nullptr); + + // Record commands. + const float clearColor[] = {0.0f, 0.2f, 0.4f, 1.0f}; + m_commandList->ClearRenderTargetView (rtvHandle, clearColor, 0, nullptr); + m_commandList->IASetPrimitiveTopology (D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + m_commandList->IASetVertexBuffers (0, 1, &m_vertexBufferView); + m_commandList->DrawInstanced (3, 1, 0, 0); + + // Indicate that the back buffer will now be used to present. + transition = CD3DX12_RESOURCE_BARRIER::Transition ( + m_renderTargets[m_view->getFrameIndex ()].Get (), D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT); + m_commandList->ResourceBarrier (1, &transition); + + ThrowIfFailed (m_commandList->Close ()); + } + + float m_aspectRatio {1.f}; + + // Pipeline objects. + ComPtr m_renderTargets[FrameCount]; + ComPtr m_rootSignature; + ComPtr m_rtvHeap; + ComPtr m_pipelineState; + ComPtr m_commandList; + UINT m_rtvDescriptorSize {0}; + + // App objects + ComPtr m_vertexBuffer; + D3D12_VERTEX_BUFFER_VIEW m_vertexBufferView; + + D3D12_VIEWPORT m_viewport {}; + D3D12_RECT m_scissorRect {}; + + struct Vertex + { + DirectX::XMFLOAT3 position; + DirectX::XMFLOAT4 color; + }; + + DirectX::XMFLOAT4 colorTop {0.f, 0.f, 1.f, 1.f}; + DirectX::XMFLOAT4 colorLeft {0.f, 1.f, 0.f, 1.f}; + DirectX::XMFLOAT4 colorRight {1.f, 0.f, 0.f, 1.f}; + uint32_t frameCounter {0}; +#if VSTGUI_USE_THREADED_DIRECT3D12_EXAMPLE + bool stopRenderThread {true}; + std::thread renderThread; +#else + SharedPointer timer; +#endif +}; + +//------------------------------------------------------------------------ +struct Direct3DController : DelegationController +{ + using DelegationController::DelegationController; + + CView* createView (const UIAttributes& attributes, const IUIDescription* description) override + { + if (auto viewName = attributes.getAttributeValue (IUIDescription::kCustomViewName)) + { + if (*viewName == "Direct3DView") + { + auto renderer = std::make_shared (); + if (auto view = ExternalView::Direct3D12View::make ( + getPlatformFactory ().asWin32Factory ()->getInstance (), renderer)) + { + return new CExternalView ({}, view); + } + } + } + return DelegationController::createView (attributes, description); + } +}; + +//------------------------------------------------------------------------ +WindowPtr makeNewDirect3DExampleWindow () +{ + auto customization = UIDesc::Customization::make (); + customization->addCreateViewControllerFunc ("Direct3DController", [] (auto, auto parent, auto) { + return new Direct3DController (parent); + }); + + UIDesc::Config config; + config.uiDescFileName = "direct3dwindow.uidesc"; + config.viewName = "view"; + config.windowConfig.type = WindowType::Document; + config.windowConfig.style.close ().size ().border (); + config.windowConfig.title = "Direct3D Example"; + config.customization = customization; + + return UIDesc::makeWindow (config); +} + +//------------------------------------------------------------------------ +} // Standalone +} // VSTGUI diff --git a/vstgui/standalone/examples/standalone/source/direct3dwindow.h b/vstgui/standalone/examples/standalone/source/direct3dwindow.h new file mode 100644 index 000000000..1810c2651 --- /dev/null +++ b/vstgui/standalone/examples/standalone/source/direct3dwindow.h @@ -0,0 +1,17 @@ +// 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 "vstgui/standalone/include/fwd.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Standalone { + +WindowPtr makeNewDirect3DExampleWindow (); + +//------------------------------------------------------------------------ +} // Standalone +} // VSTGUI diff --git a/vstgui/standalone/examples/standalone/source/metalshader.h b/vstgui/standalone/examples/standalone/source/metalshader.h new file mode 100644 index 000000000..1137582b8 --- /dev/null +++ b/vstgui/standalone/examples/standalone/source/metalshader.h @@ -0,0 +1,78 @@ +static constexpr auto shaderCode = R"( + +#include +#include + +using namespace metal; + +// Buffer index values shared between shader and C code to ensure Metal shader buffer inputs +// match Metal API buffer set calls. +typedef enum AAPLVertexInputIndex +{ + AAPLVertexInputIndexVertices = 0, + AAPLVertexInputIndexViewportSize = 1, +} AAPLVertexInputIndex; + +// This structure defines the layout of vertices sent to the vertex +// shader. This header is shared between the .metal shader and C code, to guarantee that +// the layout of the vertex array in the C code matches the layout that the .metal +// vertex shader expects. +typedef struct +{ + vector_float2 position; + vector_float4 color; +} AAPLVertex; + + + +// Vertex shader outputs and fragment shader inputs +struct RasterizerData +{ + // The [[position]] attribute of this member indicates that this value + // is the clip space position of the vertex when this structure is + // returned from the vertex function. + float4 position [[position]]; + + // Since this member does not have a special attribute, the rasterizer + // interpolates its value with the values of the other triangle vertices + // and then passes the interpolated value to the fragment shader for each + // fragment in the triangle. + float4 color; +}; + +vertex RasterizerData +vertexShader(uint vertexID [[vertex_id]], + constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]], + constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]]) +{ + RasterizerData out; + + // Index into the array of positions to get the current vertex. + // The positions are specified in pixel dimensions (i.e. a value of 100 + // is 100 pixels from the origin). + float2 pixelSpacePosition = vertices[vertexID].position.xy; + + // Get the viewport size and cast to float. + vector_float2 viewportSize = vector_float2(*viewportSizePointer); + + + // To convert from positions in pixel space to positions in clip-space, + // divide the pixel coordinates by half the size of the viewport. + out.position = vector_float4(0.0, 0.0, 0.0, 1.0); + out.position.xy = pixelSpacePosition / (viewportSize / 2.0); + + // Pass the input color directly to the rasterizer. + out.color = vertices[vertexID].color; + + return out; +} + +fragment float4 fragmentShader(RasterizerData in [[stage_in]]) +{ + // Return the interpolated color. + return in.color; +} + + +)"; + diff --git a/vstgui/standalone/examples/standalone/source/metaltypes.h b/vstgui/standalone/examples/standalone/source/metaltypes.h new file mode 100644 index 000000000..c0e9088a5 --- /dev/null +++ b/vstgui/standalone/examples/standalone/source/metaltypes.h @@ -0,0 +1,24 @@ + +#pragma once + +#include + +// Buffer index values shared between shader and C code to ensure Metal shader buffer inputs +// match Metal API buffer set calls. +typedef enum AAPLVertexInputIndex +{ + AAPLVertexInputIndexVertices = 0, + AAPLVertexInputIndexViewportSize = 1, +} AAPLVertexInputIndex; + +// This structure defines the layout of vertices sent to the vertex +// shader. This header is shared between the .metal shader and C code, to guarantee that +// the layout of the vertex array in the C code matches the layout that the .metal +// vertex shader expects. +typedef struct +{ + vector_float2 position; + vector_float4 color; +} AAPLVertex; + + diff --git a/vstgui/standalone/examples/standalone/source/metalwindow.h b/vstgui/standalone/examples/standalone/source/metalwindow.h new file mode 100644 index 000000000..7b1501075 --- /dev/null +++ b/vstgui/standalone/examples/standalone/source/metalwindow.h @@ -0,0 +1,17 @@ +// 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 "vstgui/standalone/include/fwd.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Standalone { + +WindowPtr makeNewMetalExampleWindow (); + +//------------------------------------------------------------------------ +} // Standalone +} // VSTGUI diff --git a/vstgui/standalone/examples/standalone/source/metalwindow.mm b/vstgui/standalone/examples/standalone/source/metalwindow.mm new file mode 100644 index 000000000..c813472ae --- /dev/null +++ b/vstgui/standalone/examples/standalone/source/metalwindow.mm @@ -0,0 +1,246 @@ +#import "metaltypes.h" +#import "metalwindow.h" +#import "vstgui/contrib/externalview_metal.h" +#import "vstgui/lib/cexternalview.h" +#import "vstgui/standalone/include/helpers/uidesc/customization.h" +#import "vstgui/standalone/include/helpers/windowcontroller.h" +#import "vstgui/standalone/include/iuidescwindow.h" +#import "vstgui/uidescription/delegationcontroller.h" +#import "vstgui/uidescription/iuidescription.h" +#import "vstgui/uidescription/uiattributes.h" + +#import +#import "metalshader.h" + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace Standalone { + +//------------------------------------------------------------------------ +struct ExampleMetalRenderer : ExternalView::IMetalRenderer +{ + id _device {MTLCreateSystemDefaultDevice ()}; + id _pipelineState {nullptr}; + id _commandQueue {nullptr}; + MTLRenderPassDescriptor* _drawableRenderDescriptor; + vector_uint2 _viewportSize {}; + + simd::float4 colorTop {0, 0, 1, 1}; + simd::float4 colorLeft {0, 1, 0, 1}; + simd::float4 colorRight {1, 0, 0, 1}; + + uint64_t frameCounter {0}; + + ExternalView::IMetalView* _metalView {nullptr}; + CVDisplayLinkRef _displayLink {nullptr}; + +#if !__has_feature(objc_arc) + ~ExampleMetalRenderer () noexcept + { + [_pipelineState release]; + [_commandQueue release]; + [_drawableRenderDescriptor release]; + [_device release]; + } +#endif + + bool init (ExternalView::IMetalView* metalView, CAMetalLayer* metalLayer) override + { + NSError* error = nullptr; + auto defaultLibrary = + [_device newLibraryWithSource:[NSString stringWithUTF8String:shaderCode] + options:nil + error:&error]; + if (error) + return false; + + auto vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; + auto fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"]; + if (!vertexFunction || !fragmentFunction) + return false; + + // Configure a pipeline descriptor that is used to create a pipeline state. + auto pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"Simple Pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; + + _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor + error:&error]; + +#if !__has_feature(objc_arc) + [pipelineStateDescriptor release]; + [defaultLibrary release]; + [vertexFunction release]; + [fragmentFunction release]; +#endif + if (error) + return false; + + // Create the command queue + _commandQueue = [_device newCommandQueue]; + _drawableRenderDescriptor = [MTLRenderPassDescriptor new]; + _drawableRenderDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + _drawableRenderDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + _drawableRenderDescriptor.colorAttachments[0].clearColor = MTLClearColorMake (0, 0, 0, 0); + + metalLayer.device = _device; + _metalView = metalView; + return true; + } + + void onSizeUpdate (int32_t width, int32_t height, double scaleFactor) override + { + _viewportSize.x = width * scaleFactor; + _viewportSize.y = height * scaleFactor; + } + + void updateColors () + { + ++frameCounter; + colorTop.x = (1.f + std::sin (frameCounter * 0.013f)) * 0.5f; + colorTop.y = (1.f + std::sin (frameCounter * 0.021f)) * 0.5f; + colorTop.z = (1.f + std::sin (frameCounter * 0.037f)) * 0.5f; + + colorLeft.x = (1.f + std::sin (frameCounter * 0.031f)) * 0.5f; + colorLeft.y = (1.f + std::sin (frameCounter * 0.021f)) * 0.5f; + colorLeft.z = (1.f + std::sin (frameCounter * 0.011f)) * 0.5f; + + colorRight.x = (1.f + std::sin (frameCounter * 0.025f)) * 0.5f; + colorRight.y = (1.f + std::sin (frameCounter * 0.012f)) * 0.5f; + colorRight.z = (1.f + std::sin (frameCounter * 0.031f)) * 0.5f; + } + + static CVReturn displayLinkRender (CVDisplayLinkRef displayLink, const CVTimeStamp* now, + const CVTimeStamp* outputTime, CVOptionFlags flagsIn, + CVOptionFlags* flagsOut, void* displayLinkContext) + { + auto Self = reinterpret_cast (displayLinkContext); + Self->updateColors (); + Self->_metalView->render (); + } + + void onAttached () override {} + + void onRemoved () override + { + CVDisplayLinkStop (_displayLink); + CVDisplayLinkRelease (_displayLink); + _displayLink = nullptr; + } + + void onScreenChanged (NSScreen* screen) override + { + if (_displayLink) + onRemoved (); + auto result = CVDisplayLinkCreateWithActiveCGDisplays (&_displayLink); + if (result != kCVReturnSuccess) + return; + result = CVDisplayLinkSetOutputCallback (_displayLink, displayLinkRender, this); + if (result != kCVReturnSuccess) + return; + auto displayID = static_cast ( + [screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue]); + result = CVDisplayLinkSetCurrentCGDisplay (_displayLink, displayID); + if (result != kCVReturnSuccess) + return; + CVDisplayLinkStart (_displayLink); + } + + void draw (id drawable) override + { + if (!_pipelineState) + return; + + float width = _viewportSize.x * 0.5; + float height = _viewportSize.y * 0.5; + const AAPLVertex triangleVertices[] = { + // 2D positions, RGBA colors + {{width, -height}, colorRight}, + {{-width, -height}, colorLeft}, + {{0, height}, colorTop}, + }; + + // Create a new command buffer for each render pass to the current drawable. + auto commandBuffer = [_commandQueue commandBuffer]; + commandBuffer.label = @"MyCommand"; + + _drawableRenderDescriptor.colorAttachments[0].texture = drawable.texture; + + // Create a render command encoder. + auto renderEncoder = + [commandBuffer renderCommandEncoderWithDescriptor:_drawableRenderDescriptor]; + renderEncoder.label = @"MyRenderEncoder"; + + // Set the region of the drawable to draw into. + [renderEncoder setViewport:(MTLViewport) {0.0, 0.0, static_cast (_viewportSize.x), + static_cast (_viewportSize.y), 0.0, 1.0}]; + + [renderEncoder setRenderPipelineState:_pipelineState]; + + // Pass in the parameter data. + [renderEncoder setVertexBytes:triangleVertices + length:sizeof (triangleVertices) + atIndex:AAPLVertexInputIndexVertices]; + + [renderEncoder setVertexBytes:&_viewportSize + length:sizeof (_viewportSize) + atIndex:AAPLVertexInputIndexViewportSize]; + + // Draw the triangle. + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; + + [renderEncoder endEncoding]; + + // Schedule a present once the framebuffer is complete using the current drawable. + [commandBuffer presentDrawable:drawable]; + + // Finalize rendering here & push the command buffer to the GPU. + [commandBuffer commit]; + } +}; + +//------------------------------------------------------------------------ +struct MetalController : DelegationController +{ + using DelegationController::DelegationController; + + CView* createView (const UIAttributes& attributes, const IUIDescription* description) override + { + if (auto viewName = attributes.getAttributeValue (IUIDescription::kCustomViewName)) + { + if (*viewName == "MetalView") + { + auto renderer = std::make_shared (); + if (auto metalView = ExternalView::MetalView::make (renderer)) + { + return new CExternalView ({}, metalView); + } + } + } + return DelegationController::createView (attributes, description); + } +}; + +//------------------------------------------------------------------------ +WindowPtr makeNewMetalExampleWindow () +{ + auto customization = UIDesc::Customization::make (); + customization->addCreateViewControllerFunc ( + "MetalController", [] (auto, auto parent, auto) { return new MetalController (parent); }); + + UIDesc::Config config; + config.uiDescFileName = "metalwindow.uidesc"; + config.viewName = "view"; + config.windowConfig.type = WindowType::Document; + config.windowConfig.style.close ().size ().border (); + config.windowConfig.title = "Metal Example"; + config.customization = customization; + + return UIDesc::makeWindow (config); +} + +//------------------------------------------------------------------------ +} // Standalone +} // VSTGUI diff --git a/vstgui/standalone/examples/standalone/source/testappdelegate.cpp b/vstgui/standalone/examples/standalone/source/testappdelegate.cpp index 0b510a1f2..82db77376 100644 --- a/vstgui/standalone/examples/standalone/source/testappdelegate.cpp +++ b/vstgui/standalone/examples/standalone/source/testappdelegate.cpp @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -23,8 +23,20 @@ #include "vstgui/standalone/source/genericalertbox.h" +#include "vstgui/uidescription/uiattributes.h" +#include "vstgui/uidescription/iuidescription.h" +#include "vstgui/lib/cexternalview.h" +#include "vstgui/contrib/datepicker.h" +#include "vstgui/contrib/evbutton.h" + #include +#if MAC +#include "metalwindow.h" +#elif WINDOWS +#include "direct3dwindow.h" +#endif + //------------------------------------------------------------------------ namespace MyApp { @@ -33,8 +45,8 @@ using namespace VSTGUI::Standalone; //------------------------------------------------------------------------ class Delegate : public Application::DelegateAdapter, - public ICommandHandler, - public WindowListenerAdapter + public ICommandHandler, + public WindowListenerAdapter { public: Delegate (); @@ -58,14 +70,19 @@ class Delegate : public Application::DelegateAdapter, //------------------------------------------------------------------------ Application::Init gAppDelegate (std::make_unique (), - {{Application::ConfigKey::ShowCommandsInContextMenu, 1}}); + {{Application::ConfigKey::ShowCommandsInContextMenu, 1}}); static Command NewPopup {CommandGroup::File, "New Popup"}; static Command ShowAlertBoxDesign {CommandGroup::File, "Show AlertBox Design"}; +#if MAC +static Command NewMetalExampleWindow {CommandGroup::File, "New Metal Example Window"}; +#elif WINDOWS +static Command NewDirect3DExampleWindow {CommandGroup::File, "New Direct3D Example Window"}; +#endif //------------------------------------------------------------------------ class DisabledControlsController : public DelegationController, - public ViewListenerAdapter + public ViewListenerAdapter { public: DisabledControlsController (IController* parent) : DelegationController (parent) {} @@ -77,9 +94,9 @@ class DisabledControlsController : public DelegationController, } controls.clear (); } - + CView* verifyView (CView* view, const UIAttributes& attributes, - const IUIDescription* description) override + const IUIDescription* description) override { if (auto control = dynamic_cast (view)) { @@ -118,7 +135,7 @@ class WeekdaysListConfigurator : public StaticListControlConfigurator : StaticListControlConfigurator (c.getRowHeight (), c.getFlags ()) { } - + CListControlRowDesc getRowDesc (int32_t row) const override { if (row == 0) @@ -132,13 +149,14 @@ class WeekdaysController : public DelegationController { public: WeekdaysController (IController* parent) : DelegationController (parent) {} - + CView* verifyView (CView* view, const UIAttributes& attributes, - const IUIDescription* description) override + const IUIDescription* description) override { if (auto listControl = dynamic_cast (view)) { - auto configurator = dynamic_cast (listControl->getConfigurator ()); + auto configurator = + dynamic_cast (listControl->getConfigurator ()); if (configurator) { listControl->setConfigurator (makeOwned (*configurator)); @@ -146,7 +164,58 @@ class WeekdaysController : public DelegationController } return controller->verifyView (view, attributes, description); } +}; +//------------------------------------------------------------------------ +class DatePickerController : public DelegationController +{ +public: + DatePickerController (IController* parent) : DelegationController (parent) {} + +#if MAC || WINDOWS + CView* createView (const UIAttributes& attributes, const IUIDescription* description) override + { + if (auto customViewName = attributes.getAttributeValue (IUIDescription::kCustomViewName)) + { + if (*customViewName == "DatePicker") + { + auto datePicker = std::make_shared (); + datePicker->setDate ({12, 8, 2023}); + datePicker->setChangeCallback ([] (auto date) { +#if DEBUG + DebugPrint ("%d.%d.%d\n", date.day, date.month, date.year); +#endif + }); + return new CExternalView (CRect (), datePicker); + } + if (*customViewName == "Native Checkbox") + { + auto checkbox = std::make_shared ( + ExternalView::Button::Type::Checkbox, "Checkbox"); + return new CExternalControl (CRect (), checkbox); + } + if (*customViewName == "Native Push Button") + { + auto checkbox = std::make_shared ( + ExternalView::Button::Type::Push, "Push"); + return new CExternalControl (CRect (), checkbox); + } + if (*customViewName == "Native OnOff Button") + { + auto checkbox = std::make_shared ( + ExternalView::Button::Type::OnOff, "OnOff"); + return new CExternalControl (CRect (), checkbox); + } + if (*customViewName == "Native Radio Button") + { + auto checkbox = std::make_shared ( + ExternalView::Button::Type::Radio, "Radio"); + return new CExternalControl (CRect (), checkbox); + } + } + return controller->createView (attributes, description); + } +#endif }; //------------------------------------------------------------------------ @@ -162,6 +231,11 @@ void Delegate::finishLaunching () IApplication::instance ().registerCommand (Commands::NewDocument, 'n'); IApplication::instance ().registerCommand (NewPopup, 'N'); IApplication::instance ().registerCommand (ShowAlertBoxDesign, 'b'); +#if MAC + IApplication::instance ().registerCommand (NewMetalExampleWindow, 'M'); +#elif WINDOWS + IApplication::instance ().registerCommand (NewDirect3DExampleWindow, 'D'); +#endif handleCommand (Commands::NewDocument); } @@ -177,6 +251,13 @@ void Delegate::onClosed (const IWindow& window) //------------------------------------------------------------------------ bool Delegate::canHandleCommand (const Command& command) { +#if MAC + if (command == NewMetalExampleWindow) + return true; +#elif WINDOWS + if (command == NewDirect3DExampleWindow) + return true; +#endif return command == Commands::NewDocument || command == NewPopup || command == ShowAlertBoxDesign; } @@ -201,19 +282,24 @@ bool Delegate::handleCommand (const Command& command) else { config.uiDescFileName = "test.uidesc"; - config.windowConfig.style.border (); + config.windowConfig.style.border ().size (); config.windowConfig.style.movableByWindowBackground (); auto customization = UIDesc::Customization::make (); customization->addCreateViewControllerFunc ( - "DisabledControlsController", - [] (const UTF8StringView&, IController* parent, const IUIDescription*) { - return new DisabledControlsController (parent); - }); + "DisabledControlsController", + [] (const UTF8StringView&, IController* parent, const IUIDescription*) { + return new DisabledControlsController (parent); + }); customization->addCreateViewControllerFunc ( - "WeekdaysController", - [] (const UTF8StringView&, IController* parent, const IUIDescription*) { - return new WeekdaysController (parent); - }); + "WeekdaysController", + [] (const UTF8StringView&, IController* parent, const IUIDescription*) { + return new WeekdaysController (parent); + }); + customization->addCreateViewControllerFunc ( + "DatePickerController", + [] (const UTF8StringView&, IController* parent, const IUIDescription*) { + return new DatePickerController (parent); + }); config.customization = customization; } if (auto window = UIDesc::makeWindow (config)) @@ -229,25 +315,31 @@ bool Delegate::handleCommand (const Command& command) window->show (); return true; } +#if MAC + else if (command == NewMetalExampleWindow) + { + if (auto window = makeNewMetalExampleWindow ()) + window->show (); + return true; + } +#elif WINDOWS + else if (command == NewDirect3DExampleWindow) + { + if (auto window = makeNewDirect3DExampleWindow ()) + window->show (); + return true; + } +#endif return false; } //------------------------------------------------------------------------ -void Delegate::showAboutDialog () -{ - About::show (); -} +void Delegate::showAboutDialog () { About::show (); } //------------------------------------------------------------------------ -bool Delegate::hasAboutDialog () -{ - return true; -} +bool Delegate::hasAboutDialog () { return true; } //------------------------------------------------------------------------ -VSTGUI::UTF8StringPtr Delegate::getSharedUIResourceFilename () const -{ - return "resources.uidesc"; -} +VSTGUI::UTF8StringPtr Delegate::getSharedUIResourceFilename () const { return "resources.uidesc"; } } // MyApp diff --git a/vstgui/standalone/source/platform/win32/win32window.cpp b/vstgui/standalone/source/platform/win32/win32window.cpp index cbf593031..012fa98cb 100644 --- a/vstgui/standalone/source/platform/win32/win32window.cpp +++ b/vstgui/standalone/source/platform/win32/win32window.cpp @@ -6,7 +6,6 @@ #include "win32menu.h" #include "win32async.h" -#include "../../../../lib/platform/win32/direct2d/d2ddrawcontext.h" #include "../../../../lib/platform/win32/win32directcomposition.h" #include "../../../../lib/platform/win32/win32factory.h" #include "../../../../lib/platform/win32/win32frame.h" diff --git a/vstgui/standalone/source/uidescriptionwindowcontroller.cpp b/vstgui/standalone/source/uidescriptionwindowcontroller.cpp index 10153d807..def26b93e 100644 --- a/vstgui/standalone/source/uidescriptionwindowcontroller.cpp +++ b/vstgui/standalone/source/uidescriptionwindowcontroller.cpp @@ -234,9 +234,11 @@ class ValueWrapper : public ValueListenerAdapter, void removeControl (CControl* control) { auto it = std::find (controls.begin (), controls.end (), control); - vstgui_assert (it != controls.end ()); - onRemoveControl (control); - controls.erase (it); + if (it != controls.end ()) + { + onRemoveControl (control); + controls.erase (it); + } } void viewWillDelete (CView* view) override { removeControl (dynamic_cast (view)); } diff --git a/vstgui/uidescription/editing/uibitmapscontroller.cpp b/vstgui/uidescription/editing/uibitmapscontroller.cpp index ed4e1db1b..5eeb6a8ea 100644 --- a/vstgui/uidescription/editing/uibitmapscontroller.cpp +++ b/vstgui/uidescription/editing/uibitmapscontroller.cpp @@ -18,6 +18,7 @@ #include "../../lib/cfileselector.h" #include "../../lib/idatapackage.h" #include "../../lib/dragging.h" +#include "../../lib/cdrawcontext.h" #include "../../lib/cvstguitimer.h" #include "../../lib/controls/ccolorchooser.h" #include "../../lib/controls/ctextedit.h" @@ -115,13 +116,17 @@ class UIBitmapView : public CView context->setFrameColor (kBlueCColor); context->setLineWidth (1); context->setLineStyle (kLineSolid); - context->drawLines (rowLines); - context->drawLines (colLines); + if (!rowLines.empty ()) + context->drawLines (rowLines); + if (!colLines.empty ()) + context->drawLines (colLines); context->setFrameColor (kRedCColor); context->setLineWidth (1); context->setLineStyle (lineOnOffDash2Style); - context->drawLines (rowLines); - context->drawLines (colLines); + if (!rowLines.empty ()) + context->drawLines (rowLines); + if (!colLines.empty ()) + context->drawLines (colLines); } } } @@ -685,7 +690,8 @@ void UIBitmapSettingsController::recreateBitmap () //---------------------------------------------------------------------------------------------------- void UIBitmapSettingsController::valueChanged (CControl* control) { - switch (control->getTag ()) + auto tag = control->getTag (); + switch (tag) { case kBitmapPathTag: { @@ -769,6 +775,12 @@ void UIBitmapSettingsController::valueChanged (CControl* control) desc.framesPerRow = static_cast (controls[kMultiFrameFramesPerRowTag]->getValue ()); desc.frameSize.x = controls[kMultiFrameSizeWidth]->getValue (); desc.frameSize.y = controls[kMultiFrameSizeHeight]->getValue (); + if (bitmap && tag == kMultiFrameFramesTag && desc.frameSize.x == 0. && desc.frameSize.y == 0.) + { + auto bitmapSize = bitmap->getSize (); + desc.frameSize.x = bitmapSize.x; + desc.frameSize.y = bitmapSize.y / desc.numFrames; + } actionPerformer->performBitmapMultiFrameChange (bitmapName.data (), &desc); recreateBitmap (); auto textEdit = SharedPointer (control).cast (); diff --git a/vstgui/uidescription/editing/uieditview.cpp b/vstgui/uidescription/editing/uieditview.cpp index bf70a9a35..2985803b3 100644 --- a/vstgui/uidescription/editing/uieditview.cpp +++ b/vstgui/uidescription/editing/uieditview.cpp @@ -19,6 +19,7 @@ #include "../cstream.h" #include "../detail/uiviewcreatorattributes.h" #include "../../lib/cvstguitimer.h" +#include "../../lib/cexternalview.h" #include "../../lib/cframe.h" #include "../../lib/cscrollview.h" #include "../../lib/cdropsource.h" @@ -33,7 +34,7 @@ namespace VSTGUI { namespace UIEditViewInternal { - + //---------------------------------------------------------------------------------------------------- class UISelectionView : public UIOverlayView, public UISelectionListenerAdapter //---------------------------------------------------------------------------------------------------- @@ -205,8 +206,67 @@ void UIHighlightView::draw (CDrawContext* pContext) pContext->drawRect (r, kDrawFilledAndStroked); } +//------------------------------------------------------------------------ +template +void collectExternalViewsOnInlineEditing (CViewContainer* container, T& array) +{ + container->forEachChild ([&] (auto view) { + if (view.template cast ()) + array.emplace_back (view); + else if (auto c = view->asViewContainer ()) + collectExternalViewsOnInlineEditing (c, array); + }); +} + } // UIEditViewInternal +//------------------------------------------------------------------------ +struct UIEditView::ViewAddedObserver : IViewAddedRemovedObserver, + ViewListenerAdapter +{ + ~ViewAddedObserver () override + { + for (auto view : views) + { + if (auto viewEmbedder = dynamic_cast (view)) + { + if (auto ev = viewEmbedder->getExternalView ()) + ev->setMouseEnabled (view->getMouseEnabled ()); + } + view->unregisterViewListener (this); + } + } + void onViewAdded (CFrame* frame, CView* view) override + { + if (auto viewEmbedder = dynamic_cast (view)) + { + if (auto ev = viewEmbedder->getExternalView ()) + ev->setMouseEnabled (false); + view->registerViewListener (this); + views.emplace_back (view); + } + } + void onViewRemoved (CFrame* frame, CView* view) override {} + + void viewWillDelete (CView* view) override + { + view->unregisterViewListener (this); + auto it = std::find (views.begin (), views.end (), view); + if (it != views.end ()) + views.erase (it); + } + void viewOnMouseEnabled (CView* view, bool state) override + { + if (auto viewEmbedder = dynamic_cast (view)) + { + if (auto ev = viewEmbedder->getExternalView ()) + ev->setMouseEnabled (false); + } + } + + std::vector views; +}; + //---------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------- @@ -274,7 +334,7 @@ void UIEditView::enableEditing (bool state) CFrame* parent = getFrame (); if (parent == nullptr) return; - + if (editing) { CRect r = parent->getViewSize (); @@ -286,7 +346,7 @@ void UIEditView::enableEditing (bool state) overlayView->setTransparency (true); overlayView->setZIndex (std::numeric_limits::max () - 1); parent->addView (overlayView); - + highlightView = new UIEditViewInternal::UIHighlightView (this, viewHighlightColor); overlayView->addView (highlightView); auto selectionView = new UIEditViewInternal::UISelectionView (this, getSelection (), viewSelectionColor, kResizeHandleSize); @@ -299,6 +359,25 @@ void UIEditView::enableEditing (bool state) highlightView = nullptr; lines = nullptr; } + disableExternalViewsOnInlineEditing (editing); + } +} + +//------------------------------------------------------------------------ +void UIEditView::disableExternalViewsOnInlineEditing (bool state) +{ + CFrame* parent = getFrame (); + if (editingViewAddedObserver) + parent->setViewAddedRemovedObserver (nullptr); + editingViewAddedObserver.reset (); + if (state) + { + editingViewAddedObserver = std::make_unique (); + std::vector views; + UIEditViewInternal::collectExternalViewsOnInlineEditing (this, views); + for (auto* v : views) + editingViewAddedObserver->onViewAdded (parent, v); + parent->setViewAddedRemovedObserver (editingViewAddedObserver.get ()); } } @@ -349,6 +428,7 @@ void UIEditView::setEditView (CView* view) { if (view != getEditView ()) { + disableExternalViewsOnInlineEditing (false); invalid (); removeAll (); CRect vs (getViewSize ()); @@ -364,6 +444,7 @@ void UIEditView::setEditView (CView* view) setMouseableArea (vs); setViewSize (vs); } + disableExternalViewsOnInlineEditing (editing); invalid (); } } @@ -421,7 +502,7 @@ void UIEditView::drawRect (CDrawContext *pContext, const CRect& updateRect) if (!editing && focusDrawing) getFrame ()->setFocusDrawingEnabled (focusDrawing); - + pContext->setClipRect (updateRect); CDrawContext::Transform transform (*pContext, CGraphicsTransform ().translate (getViewSize ().left, getViewSize ().top)); @@ -1342,6 +1423,12 @@ bool UIEditView::attached (CView* parent) //----------------------------------------------------------------------------- bool UIEditView::removed (CView* parent) { + auto frame = getFrame (); + if (editingViewAddedObserver) + { + frame->setViewAddedRemovedObserver (nullptr); + editingViewAddedObserver.reset (); + } IController* controller = getViewController (this, true); if (controller) { @@ -1351,9 +1438,10 @@ bool UIEditView::removed (CView* parent) } if (overlayView) { - getFrame()->removeView (overlayView); + frame->removeView (overlayView); overlayView = nullptr; } + frame->setCursor (kCursorDefault); return CViewContainer::removed (parent); } diff --git a/vstgui/uidescription/editing/uieditview.h b/vstgui/uidescription/editing/uieditview.h index d2069a73f..34fa18200 100644 --- a/vstgui/uidescription/editing/uieditview.h +++ b/vstgui/uidescription/editing/uieditview.h @@ -140,6 +140,10 @@ class UIEditView : public CViewContainer, public IDropTarget CColor lassoFrameColor; CColor viewHighlightColor; CColor viewSelectionColor; + + struct ViewAddedObserver; + std::unique_ptr editingViewAddedObserver; + void disableExternalViewsOnInlineEditing (bool state); }; } // VSTGUI diff --git a/vstgui/uidescription/editing/uigradientscontroller.cpp b/vstgui/uidescription/editing/uigradientscontroller.cpp index 1e1e63d80..3909780ac 100644 --- a/vstgui/uidescription/editing/uigradientscontroller.cpp +++ b/vstgui/uidescription/editing/uigradientscontroller.cpp @@ -15,6 +15,7 @@ #include "../../lib/cgradientview.h" #include "../../lib/ifocusdrawing.h" #include "../../lib/cgraphicspath.h" +#include "../../lib/cdrawcontext.h" #include "../../lib/events.h" #include diff --git a/vstgui/uidescription/editing/uiselection.cpp b/vstgui/uidescription/editing/uiselection.cpp index 41a5f0f7f..16bdc13bf 100644 --- a/vstgui/uidescription/editing/uiselection.cpp +++ b/vstgui/uidescription/editing/uiselection.cpp @@ -326,16 +326,7 @@ SharedPointer createBitmapFromSelection (UISelection* selection, CFrame CDrawContext::Transform transform (context, CGraphicsTransform ().translate (p.x, p.y)); context.setClipRect (view->getViewSize ()); - if (IPlatformViewLayerDelegate* layer = view.cast ()) - { - CRect r (view->getViewSize ()); - r.originize (); - layer->drawViewLayer (&context, r); - } - else - { - view->drawRect (&context, view->getViewSize ()); - } + view->drawRect (&context, view->getViewSize ()); } } if (anchorView && anchorView->isAttached ()) diff --git a/vstgui/vstgui.cpp b/vstgui/vstgui.cpp index 66c4dc818..18c979d6f 100644 --- a/vstgui/vstgui.cpp +++ b/vstgui/vstgui.cpp @@ -10,6 +10,7 @@ #include "lib/cdrawcontext.cpp" #include "lib/cdrawmethods.cpp" #include "lib/cdropsource.cpp" +#include "lib/cexternalview.cpp" #include "lib/cfileselector.cpp" #include "lib/cfont.cpp" #include "lib/cframe.cpp" diff --git a/vstgui/vstgui_ios.mm b/vstgui/vstgui_ios.mm index 6d1cc7904..73325661c 100644 --- a/vstgui/vstgui_ios.mm +++ b/vstgui/vstgui_ios.mm @@ -4,7 +4,7 @@ #import "vstgui.cpp" -#import "lib/platform/mac/cgdrawcontext.cpp" +#import "lib/platform/mac/coregraphicsdevicecontext.mm" #import "lib/platform/mac/macglobals.cpp" #import "lib/platform/mac/cgbitmap.cpp" #import "lib/platform/mac/quartzgraphicspath.cpp" diff --git a/vstgui/vstgui_linux.cpp b/vstgui/vstgui_linux.cpp index 2eef695a3..4f5ed8c21 100644 --- a/vstgui/vstgui_linux.cpp +++ b/vstgui/vstgui_linux.cpp @@ -10,7 +10,7 @@ #include "lib/platform/linux/x11utils.cpp" #include "lib/platform/linux/cairobitmap.cpp" -#include "lib/platform/linux/cairocontext.cpp" +#include "lib/platform/linux/cairographicscontext.cpp" #include "lib/platform/linux/cairofont.cpp" #include "lib/platform/linux/cairogradient.cpp" #include "lib/platform/linux/cairopath.cpp" diff --git a/vstgui/vstgui_mac.mm b/vstgui/vstgui_mac.mm index c19da63cd..30ce1063c 100644 --- a/vstgui/vstgui_mac.mm +++ b/vstgui/vstgui_mac.mm @@ -4,9 +4,9 @@ #import "vstgui.cpp" -#import "lib/platform/mac/cgdrawcontext.cpp" #import "lib/platform/mac/macglobals.cpp" #import "lib/platform/mac/cgbitmap.cpp" +#import "lib/platform/mac/coregraphicsdevicecontext.mm" #import "lib/platform/mac/quartzgraphicspath.cpp" #import "lib/platform/mac/macfactory.mm" #import "lib/platform/mac/macfileselector.mm" diff --git a/vstgui/vstgui_win32.cpp b/vstgui/vstgui_win32.cpp index d2c03ef84..b853aad68 100644 --- a/vstgui/vstgui_win32.cpp +++ b/vstgui/vstgui_win32.cpp @@ -1,4 +1,4 @@ -// This file is part of VSTGUI. It is subject to the license terms +// 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 @@ -24,7 +24,7 @@ #include "lib/platform/win32/wintimer.cpp" #include "lib/platform/win32/direct2d/d2dbitmap.cpp" #include "lib/platform/win32/direct2d/d2dbitmapcache.cpp" -#include "lib/platform/win32/direct2d/d2ddrawcontext.cpp" #include "lib/platform/win32/direct2d/d2dfont.cpp" #include "lib/platform/win32/direct2d/d2dgradient.cpp" #include "lib/platform/win32/direct2d/d2dgraphicspath.cpp" +#include "lib/platform/win32/direct2d/d2dgraphicscontext.cpp"