use std::cell::RefCell;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::time::Instant;
use denise::{InputEvent, Rect, Role, Size, Surface, Theme};
use denise_ui::widgets::{Button, Label, Panel, TextInput};
use denise_ui::{NodeId, Ui};
use denise_win32::{ControlDelegate, DeniseControl, DibSurface};
use windows::Win32::Foundation::{
DV_E_DVASPECT, E_FAIL, E_NOINTERFACE, E_NOTIMPL, E_POINTER, HWND, RECT, RECTL, SIZE,
};
use windows::Win32::Graphics::Gdi::{
HALFTONE, HDC, LOGPALETTE, RestoreDC, SRCCOPY, SaveDC, SetBrushOrgEx, SetStretchBltMode,
StretchBlt,
};
use windows::Win32::System::Com::{
ADVF_ONLYONCE, CoTaskMemAlloc, DISPATCH_METHOD, DISPPARAMS, DVASPECT, DVASPECT_CONTENT,
DVTARGETDEVICE, IAdviseSink, IDataObject, IDispatch, IEnumSTATDATA, IMoniker, IPersist,
IPersist_Impl, IPersistStreamInit, IPersistStreamInit_Impl, IStream, ITypeInfo,
};
use windows::Win32::System::Diagnostics::Debug::IObjectSafety_Impl;
use windows::Win32::System::Ole::{
IEnumOLEVERB, IOleClientSite, IOleControl_Impl, IOleInPlaceObject_Impl, IOleInPlaceSite,
IOleObject_Impl, IOleWindow_Impl, IViewObject_Impl, IViewObject2_Impl, OLECLOSE, OLEGETMONIKER,
OLEIVERB_HIDE, OLEIVERB_INPLACEACTIVATE, OLEIVERB_SHOW, OLEIVERB_UIACTIVATE, OLEMISC,
OLEWHICHMK, USERCLASSTYPE,
};
use windows::Win32::UI::WindowsAndMessaging::{DestroyWindow, SWP_NOZORDER, SetWindowPos};
use windows_core::{BOOL, GUID, HRESULT, Interface, OutRef, Ref, implement};
use crate::dispatch;
use crate::himetric::{himetric_to_pixels, pixels_to_himetric};
use crate::model::{Model, Shared};
use crate::registry::MISC_STATUS;
use crate::safety;
use crate::server::CLSID_DENISE_PANEL;
use crate::view;
const MSG_ACTIVATED: u32 = 1;
#[implement(
windows::Win32::System::Ole::IOleObject,
windows::Win32::System::Ole::IOleInPlaceObject,
windows::Win32::System::Ole::IOleControl,
windows::Win32::System::Com::IPersistStreamInit,
windows::Win32::System::Com::IDispatch,
windows::Win32::System::Com::IConnectionPointContainer,
windows::Win32::System::Com::IConnectionPoint,
windows::Win32::System::Ole::IViewObject2,
windows::Win32::System::Diagnostics::Debug::IObjectSafety
)]
pub struct DenisePanel {
pub(crate) state: RefCell<PanelState>,
pub(crate) model: Shared,
}
pub(crate) struct PanelState {
site: Option<IOleClientSite>,
control: Option<DeniseControl>,
extent: SIZE,
position: RECT,
initialised: bool,
view_sink: Option<IAdviseSink>,
view_aspects: u32,
view_advf: u32,
pub(crate) type_info: Option<ITypeInfo>,
}
impl Default for DenisePanel {
fn default() -> Self {
Self::new()
}
}
impl DenisePanel {
pub fn new() -> Self {
Self {
state: RefCell::new(PanelState {
site: None,
control: None,
extent: SIZE {
cx: pixels_to_himetric(200),
cy: pixels_to_himetric(120),
},
position: RECT::default(),
initialised: false,
view_sink: None,
view_aspects: 0,
view_advf: 0,
type_info: None,
}),
model: Model::new(),
}
}
}
impl DenisePanel_Impl {
fn activate(&self) -> windows_core::Result<()> {
if self.state.borrow().control.is_some() {
return Ok(());
}
let site = self
.state
.borrow()
.site
.clone()
.ok_or_else(|| windows_core::Error::from(E_FAIL))?;
let in_place: IOleInPlaceSite = site.cast()?;
let parent: HWND = unsafe { in_place.GetWindow() }?;
unsafe { in_place.OnInPlaceActivate() }?;
let (position, extent) = {
let state = self.state.borrow();
(state.position, state.extent)
};
let width = position.right - position.left;
let height = position.bottom - position.top;
let bounds = if width > 0 && height > 0 {
Rect::new(position.left, position.top, width, height)
} else {
Rect::new(
position.left,
position.top,
himetric_to_pixels(extent.cx).max(1),
himetric_to_pixels(extent.cy).max(1),
)
};
let size = Size::new(bounds.width as u32, bounds.height as u32);
let tree = Tree::new(size, self.model.clone());
let control = DeniseControl::new(parent, bounds, 1.0, Box::new(tree))
.map_err(|_| windows_core::Error::from(E_FAIL))?;
self.state.borrow_mut().control = Some(control);
control.update();
Ok(())
}
fn deactivate(&self) {
let control = self.state.borrow_mut().control.take();
if let Some(control) = control {
unsafe {
let _ = DestroyWindow(control.hwnd());
}
}
}
pub(crate) fn sync(&self) {
if self.model.borrow().inside {
return;
}
let control = self.state.borrow().control;
match control {
Some(control) => {
let _ = catch_unwind(AssertUnwindSafe(|| control.update()));
}
None => self.notify_view(),
}
}
fn notify_view(&self) {
let (sink, aspects, once) = {
let state = self.state.borrow();
let once = state.view_advf & ADVF_ONLYONCE.0 as u32 != 0;
(state.view_sink.clone(), state.view_aspects, once)
};
let Some(sink) = sink else {
return;
};
if once {
self.state.borrow_mut().view_sink = None;
}
unsafe { sink.OnViewChange(aspects, -1) };
}
fn render(&self, target: HDC, bounds: *const RECTL) -> windows_core::Result<()> {
if bounds.is_null() {
return Err(E_POINTER.into());
}
let bounds = unsafe { *bounds };
let Some(plan) = view::plan(bounds.left, bounds.top, bounds.right, bounds.bottom) else {
return Ok(());
};
let size = Size::new(plan.source_width, plan.source_height);
let mut surface =
DibSurface::new(size, 1.0).map_err(|_| windows_core::Error::from(E_FAIL))?;
let (mut ui, _nodes) = build(size, &self.model);
ui.tick(0);
ui.invalidate_all();
{
let mut frame = surface
.acquire()
.map_err(|_| windows_core::Error::from(E_FAIL))?;
ui.paint(&mut frame);
}
let drawn = unsafe {
let saved = SaveDC(target);
if plan.stretches() {
SetStretchBltMode(target, HALFTONE);
let _ = SetBrushOrgEx(target, 0, 0, None);
}
let drawn = StretchBlt(
target,
plan.x,
plan.y,
plan.width,
plan.height,
Some(surface.dc()),
0,
0,
size.width as i32,
size.height as i32,
SRCCOPY,
);
if saved != 0 {
let _ = RestoreDC(target, saved);
}
drawn
};
if drawn.as_bool() {
Ok(())
} else {
Err(E_FAIL.into())
}
}
}
impl IOleObject_Impl for DenisePanel_Impl {
fn SetClientSite(&self, site: Ref<'_, IOleClientSite>) -> windows_core::Result<()> {
self.state.borrow_mut().site = site.cloned();
Ok(())
}
fn GetClientSite(&self) -> windows_core::Result<IOleClientSite> {
self.state
.borrow()
.site
.clone()
.ok_or_else(|| windows_core::Error::from(E_FAIL))
}
fn SetHostNames(
&self,
_container: &windows_core::PCWSTR,
_object: &windows_core::PCWSTR,
) -> windows_core::Result<()> {
Ok(())
}
fn Close(&self, _save: &OLECLOSE) -> windows_core::Result<()> {
self.deactivate();
{
let mut state = self.state.borrow_mut();
state.site = None;
state.view_sink = None;
}
self.model.borrow_mut().clear_sinks();
Ok(())
}
fn SetMoniker(
&self,
_which: &OLEWHICHMK,
_moniker: Ref<'_, IMoniker>,
) -> windows_core::Result<()> {
Err(E_FAIL.into())
}
fn GetMoniker(
&self,
_assign: &OLEGETMONIKER,
_which: &OLEWHICHMK,
) -> windows_core::Result<IMoniker> {
Err(E_FAIL.into())
}
fn InitFromData(
&self,
_data: Ref<'_, IDataObject>,
_creation: BOOL,
_reserved: u32,
) -> windows_core::Result<()> {
Err(E_FAIL.into())
}
fn GetClipboardData(&self, _reserved: u32) -> windows_core::Result<IDataObject> {
Err(E_FAIL.into())
}
fn DoVerb(
&self,
verb: i32,
_message: *const windows::Win32::UI::WindowsAndMessaging::MSG,
_site: Ref<'_, IOleClientSite>,
_index: i32,
_parent: HWND,
position: *const RECT,
) -> windows_core::Result<()> {
if !position.is_null() {
self.state.borrow_mut().position = unsafe { *position };
}
if verb == OLEIVERB_SHOW.0
|| verb == OLEIVERB_INPLACEACTIVATE.0
|| verb == OLEIVERB_UIACTIVATE.0
{
self.activate()
} else if verb == OLEIVERB_HIDE.0 {
self.deactivate();
Ok(())
} else {
Err(E_FAIL.into())
}
}
fn EnumVerbs(&self) -> windows_core::Result<IEnumOLEVERB> {
Err(E_FAIL.into())
}
fn Update(&self) -> windows_core::Result<()> {
self.sync();
Ok(())
}
fn IsUpToDate(&self) -> windows_core::Result<()> {
Ok(())
}
fn GetUserClassID(&self) -> windows_core::Result<GUID> {
Ok(CLSID_DENISE_PANEL)
}
fn GetUserType(&self, _form: &USERCLASSTYPE) -> windows_core::Result<windows_core::PWSTR> {
let wide: Vec<u16> = crate::registry::FRIENDLY_NAME
.encode_utf16()
.chain(core::iter::once(0))
.collect();
let bytes = core::mem::size_of_val(wide.as_slice());
let buffer = unsafe { CoTaskMemAlloc(bytes) } as *mut u16;
if buffer.is_null() {
return Err(windows::Win32::Foundation::E_OUTOFMEMORY.into());
}
unsafe { core::ptr::copy_nonoverlapping(wide.as_ptr(), buffer, wide.len()) };
Ok(windows_core::PWSTR(buffer))
}
fn SetExtent(&self, _aspect: DVASPECT, size: *const SIZE) -> windows_core::Result<()> {
if size.is_null() {
return Err(E_POINTER.into());
}
self.state.borrow_mut().extent = unsafe { *size };
Ok(())
}
fn GetExtent(&self, _aspect: DVASPECT) -> windows_core::Result<SIZE> {
Ok(self.state.borrow().extent)
}
fn Advise(&self, _sink: Ref<'_, IAdviseSink>) -> windows_core::Result<u32> {
Ok(0)
}
fn Unadvise(&self, _token: u32) -> windows_core::Result<()> {
Ok(())
}
fn EnumAdvise(&self) -> windows_core::Result<IEnumSTATDATA> {
Err(E_FAIL.into())
}
fn GetMiscStatus(&self, _aspect: DVASPECT) -> windows_core::Result<OLEMISC> {
Ok(OLEMISC(MISC_STATUS as i32))
}
fn SetColorScheme(
&self,
_palette: *const windows::Win32::Graphics::Gdi::LOGPALETTE,
) -> windows_core::Result<()> {
Ok(())
}
}
impl IOleWindow_Impl for DenisePanel_Impl {
fn GetWindow(&self) -> windows_core::Result<HWND> {
self.state
.borrow()
.control
.map(|c| c.hwnd())
.ok_or_else(|| windows_core::Error::from(E_FAIL))
}
fn ContextSensitiveHelp(&self, _entering: BOOL) -> windows_core::Result<()> {
Ok(())
}
}
impl IOleInPlaceObject_Impl for DenisePanel_Impl {
fn InPlaceDeactivate(&self) -> windows_core::Result<()> {
self.deactivate();
Ok(())
}
fn UIDeactivate(&self) -> windows_core::Result<()> {
Ok(())
}
fn SetObjectRects(
&self,
position: *const RECT,
_clip: *const RECT,
) -> windows_core::Result<()> {
if position.is_null() {
return Err(E_POINTER.into());
}
let position = unsafe { *position };
self.state.borrow_mut().position = position;
let control = self.state.borrow().control;
if let Some(control) = control {
unsafe {
let _ = SetWindowPos(
control.hwnd(),
None,
position.left,
position.top,
position.right - position.left,
position.bottom - position.top,
SWP_NOZORDER,
);
}
}
Ok(())
}
fn ReactivateAndUndo(&self) -> windows_core::Result<()> {
Err(E_FAIL.into())
}
}
impl IOleControl_Impl for DenisePanel_Impl {
fn GetControlInfo(
&self,
_info: *mut windows::Win32::System::Ole::CONTROLINFO,
) -> windows_core::Result<()> {
Err(E_FAIL.into())
}
fn OnMnemonic(
&self,
_message: *const windows::Win32::UI::WindowsAndMessaging::MSG,
) -> windows_core::Result<()> {
Err(E_FAIL.into())
}
fn OnAmbientPropertyChange(&self, _dispid: i32) -> windows_core::Result<()> {
Ok(())
}
fn FreezeEvents(&self, _freeze: BOOL) -> windows_core::Result<()> {
Ok(())
}
}
impl IViewObject_Impl for DenisePanel_Impl {
fn Draw(
&self,
aspect: DVASPECT,
_index: i32,
_aspect_info: *mut core::ffi::c_void,
_target_device: *const DVTARGETDEVICE,
_target_dc: HDC,
draw_dc: HDC,
bounds: *const RECTL,
_window_bounds: *const RECTL,
_continue_fn: isize,
_continue_arg: usize,
) -> windows_core::Result<()> {
if aspect != DVASPECT_CONTENT {
return Err(DV_E_DVASPECT.into());
}
catch_unwind(AssertUnwindSafe(|| self.render(draw_dc, bounds)))
.unwrap_or_else(|_| Err(E_FAIL.into()))
}
fn GetColorSet(
&self,
_aspect: DVASPECT,
_index: i32,
_aspect_info: *mut core::ffi::c_void,
_target_device: *const DVTARGETDEVICE,
_target_dc: HDC,
_colours: *mut *mut LOGPALETTE,
) -> windows_core::Result<()> {
Err(E_NOTIMPL.into())
}
fn Freeze(
&self,
_aspect: DVASPECT,
_index: i32,
_aspect_info: *mut core::ffi::c_void,
_token: *mut u32,
) -> windows_core::Result<()> {
Err(E_NOTIMPL.into())
}
fn Unfreeze(&self, _token: u32) -> windows_core::Result<()> {
Err(E_NOTIMPL.into())
}
fn SetAdvise(
&self,
aspects: DVASPECT,
advf: u32,
sink: Ref<'_, IAdviseSink>,
) -> windows_core::Result<()> {
let mut state = self.state.borrow_mut();
state.view_sink = sink.cloned();
state.view_aspects = aspects.0;
state.view_advf = advf;
Ok(())
}
fn GetAdvise(
&self,
aspects: *mut u32,
advf: *mut u32,
sink: OutRef<'_, IAdviseSink>,
) -> windows_core::Result<()> {
let state = self.state.borrow();
if !aspects.is_null() {
unsafe { *aspects = state.view_aspects };
}
if !advf.is_null() {
unsafe { *advf = state.view_advf };
}
if !sink.is_null() {
sink.write(state.view_sink.clone())?;
}
Ok(())
}
}
impl IViewObject2_Impl for DenisePanel_Impl {
fn GetExtent(
&self,
_aspect: DVASPECT,
_index: i32,
_target_device: *const DVTARGETDEVICE,
) -> windows_core::Result<SIZE> {
Ok(self.state.borrow().extent)
}
}
impl IObjectSafety_Impl for DenisePanel_Impl {
fn GetInterfaceSafetyOptions(
&self,
riid: *const GUID,
supported: *mut u32,
enabled: *mut u32,
) -> windows_core::Result<()> {
if supported.is_null() || enabled.is_null() {
return Err(E_POINTER.into());
}
let options = safety::supported(asked_about(riid));
if options == 0 {
return Err(E_NOINTERFACE.into());
}
unsafe {
*supported = options;
*enabled = options;
}
Ok(())
}
fn SetInterfaceSafetyOptions(
&self,
riid: *const GUID,
mask: u32,
_enabled: u32,
) -> windows_core::Result<()> {
let options = safety::supported(asked_about(riid));
if options == 0 {
return Err(E_NOINTERFACE.into());
}
if safety::accepts(options, mask) {
Ok(())
} else {
Err(E_FAIL.into())
}
}
}
fn asked_about(riid: *const GUID) -> safety::Asked {
if riid.is_null() {
return safety::Asked::Other;
}
let riid = unsafe { *riid };
if riid == IDispatch::IID || riid == IID_IDISPATCHEX {
safety::Asked::Automation
} else if riid == IPersistStreamInit::IID || riid == IPersist::IID {
safety::Asked::Persistence
} else {
safety::Asked::Other
}
}
const IID_IDISPATCHEX: GUID = GUID::from_u128(0xa6ef9860_c720_11d0_9337_00a0c90dcaa9);
impl IPersist_Impl for DenisePanel_Impl {
fn GetClassID(&self) -> windows_core::Result<GUID> {
Ok(CLSID_DENISE_PANEL)
}
}
impl IPersistStreamInit_Impl for DenisePanel_Impl {
fn IsDirty(&self) -> HRESULT {
windows::Win32::Foundation::S_FALSE
}
fn Load(&self, _stream: Ref<'_, IStream>) -> windows_core::Result<()> {
self.state.borrow_mut().initialised = true;
Ok(())
}
fn Save(&self, _stream: Ref<'_, IStream>, _clear_dirty: BOOL) -> windows_core::Result<()> {
Ok(())
}
fn GetSizeMax(&self) -> windows_core::Result<u64> {
Ok(0)
}
fn InitNew(&self) -> windows_core::Result<()> {
self.state.borrow_mut().initialised = true;
Ok(())
}
}
#[derive(Clone, Copy, Default)]
struct Nodes {
label: Option<NodeId>,
input: Option<NodeId>,
button: Option<NodeId>,
}
struct Tree {
ui: Ui<u32>,
nodes: Nodes,
model: Shared,
started: Instant,
}
impl Tree {
fn new(size: Size, model: Shared) -> Self {
let (ui, nodes) = build(size, &model);
Self {
ui,
nodes,
model,
started: Instant::now(),
}
}
fn apply(&mut self) {
let (text, caption, enabled, dirty, refresh) = {
let mut model = self.model.borrow_mut();
let pending = (
model.text.clone(),
model.caption.clone(),
model.enabled,
model.dirty,
model.refresh,
);
model.dirty = false;
model.refresh = false;
pending
};
if refresh {
self.ui.invalidate_all();
}
if !dirty {
return;
}
if let Some(label) = self
.nodes
.label
.and_then(|id| self.ui.widget_mut::<Label>(id))
{
label.set_text(caption);
}
let stale = self.nodes.input.filter(|id| {
self.ui
.widget::<TextInput<u32>>(*id)
.is_some_and(|field| field.text() != text)
});
if let Some(field) = stale.and_then(|id| self.ui.widget_mut::<TextInput<u32>>(id)) {
field.set_text(text);
}
for id in [self.nodes.input, self.nodes.button].into_iter().flatten() {
self.ui.set_enabled(id, enabled);
}
}
fn collect(&mut self) -> Vec<i32> {
let clicked = self
.ui
.drain_messages()
.any(|message| message == MSG_ACTIVATED);
let current = self
.nodes
.input
.and_then(|id| self.ui.widget::<TextInput<u32>>(id))
.map(|field| field.text().to_string());
match current {
Some(current) => {
let raised = dispatch::events_raised(&self.model.borrow().text, ¤t, clicked);
self.model.borrow_mut().text = current;
raised
}
None => dispatch::events_raised("", "", clicked),
}
}
fn raise(&self, dispid: i32) {
let sinks = self.model.borrow().sinks();
let params = DISPPARAMS::default();
for sink in sinks {
unsafe {
let _ = sink.Invoke(
dispid,
&GUID::zeroed(),
0,
DISPATCH_METHOD,
¶ms,
None,
None,
None,
);
}
}
}
}
struct Running(Shared);
impl Running {
fn enter(model: &Shared) -> Self {
model.borrow_mut().inside = true;
Self(model.clone())
}
}
impl Drop for Running {
fn drop(&mut self) {
self.0.borrow_mut().inside = false;
}
}
fn build(size: Size, model: &Shared) -> (Ui<u32>, Nodes) {
let (text, caption, enabled) = {
let model = model.borrow();
(model.text.clone(), model.caption.clone(), model.enabled)
};
let mut ui: Ui<u32> = Ui::new(size, Theme::DARK);
ui.show_cursor(false);
let root = ui.root();
let width = size.width as i32;
let height = size.height as i32;
let mut nodes = Nodes::default();
if let Some(card) = ui.add(
root,
Panel::default(),
Rect::new(8, 8, (width - 16).max(1), (height - 16).max(1)),
) {
nodes.label = ui.add(
card,
Label::new(caption),
Rect::new(12, 10, (width - 40).max(1), 22),
);
let mut field = TextInput::<u32>::new().with_placeholder("Tekst");
field.set_text(text);
nodes.input = ui.add(card, field, Rect::new(12, 38, (width - 40).max(1), 32));
nodes.button = ui.add(
card,
Button::new("OK", MSG_ACTIVATED).with_role(Role::Primary),
Rect::new(12, 78, 96, 30),
);
}
for id in [nodes.input, nodes.button].into_iter().flatten() {
ui.set_enabled(id, enabled);
}
(ui, nodes)
}
impl ControlDelegate for Tree {
fn update(&mut self, surface: &mut DibSurface, events: &[InputEvent], damage: &mut Vec<Rect>) {
if surface.size() != self.ui.size() {
let (ui, nodes) = build(surface.size(), &self.model);
self.ui = ui;
self.nodes = nodes;
self.ui.invalidate_all();
}
let _running = Running::enter(&self.model);
self.apply();
self.ui.handle(events);
self.ui.tick(self.started.elapsed().as_millis() as u64);
for dispid in self.collect() {
self.raise(dispid);
}
self.apply();
if !self.ui.needs_paint() {
return;
}
if let Ok(mut frame) = surface.acquire() {
self.ui.paint(&mut frame);
drop(frame);
damage.extend_from_slice(self.ui.damage());
self.ui.presented();
}
}
fn next_wake_ms(&self) -> Option<u64> {
self.ui.next_wake_ms()
}
}
const _: u32 = MISC_STATUS;