// snap_helper.h >>
#pragma once
#ifndef __file__SNAP_HELPER_H
#define __file__SNAP_HELPER_H
#include <vector>
#include <algorithm>
#include <functional>
#include "windowsx.h"
#include "windows.h"
class snap_helper
{
HWND m_wnd;
struct wnd_data
{
HWND m_wnd;
bool m_is_neighbour;
wnd_data() : m_wnd(0), m_is_neighbour(false) {}
wnd_data( HWND w ) : m_wnd(w), m_is_neighbour(false) {}
wnd_data( HWND w, bool neighbour ) : m_wnd(w), m_is_neighbour(neighbour) {}
};
//typedef std::list<wnd_data> container_wnd;
typedef std::vector<wnd_data> container_wnd;
static container_wnd s_wnd_list;
static HWND s_master_wnd;
static int s_thresold;
enum DRAG_ANCHOR_X { DAX_UNKNOWN, DAX_LEFT, DAX_RIGHT, };
enum DRAG_ANCHOR_Y { DAY_UNKNOWN, DAY_TOP, DAY_BOTTOM, };
DRAG_ANCHOR_X m_drag_anchor_x;
DRAG_ANCHOR_Y m_drag_anchor_y;
int m_drag_err_x;
int m_drag_err_y;
class is_eq_wnd : public std::unary_function<wnd_data, bool>
{
const HWND match;
public:
is_eq_wnd( HWND m ) : match(m) {}
bool operator() ( wnd_data& val ) { return val.m_wnd == match; }
};
static void remove_window( HWND wnd )
{
container_wnd::iterator i = remove_if( s_wnd_list.begin(), s_wnd_list.end(), is_eq_wnd(wnd) );
s_wnd_list.erase( i, s_wnd_list.end() );
}
static void append_window( HWND wnd )
{
container_wnd::iterator i = find_if( s_wnd_list.begin(), s_wnd_list.end(), is_eq_wnd(wnd) );
if( i == s_wnd_list.end() ) s_wnd_list.push_back( wnd );
}
public:
static int thresold() { return s_thresold; }
static void thresold( int t ) { s_thresold = t; }
static bool is_master( HWND wnd ) { return wnd == s_master_wnd; }
bool is_master() const { return is_master( m_wnd ); }
void init( HWND wnd, bool is_master )
{
m_wnd = wnd;
append_window( wnd );
if( is_master ) s_master_wnd = m_wnd;
}
snap_helper( HWND wnd, bool is_master )
{
init( wnd, is_master );
}
snap_helper()
{
}
~snap_helper()
{
if( s_master_wnd == m_wnd ) s_master_wnd = 0;
remove_window( m_wnd );
}
private:
//////////////////////////////////////////////////////////////////////////
// helpers methods
//////////////////////////////////////////////////////////////////////////
// расстояние достаточно близкое чтобы притянуться
static bool is_close( int dx )
{
return dx > -s_thresold && dx < s_thresold;
}
// точка pt внутри отрезка [x1, x2]?
static bool pt_is_inside( int x1, int x2, int pt )
{
return pt >= x1 && pt <= x2;
}
// отрезки [p1_x1, p1_x2], [p2_x1, p2_x2]
// пересекаються или прилегают друг к другу?
static bool is_intersect( int p1_x1, int p1_x2, int p2_x1, int p2_x2 );
// pt1 < pt2
// Возвращает pt1 либо pt2 -- к чему ближе pt
static int nearest_dest( int pt1, int pt2, int pt )
{
return abs(pt1-pt) < abs(pt2-pt) ? pt1-pt : pt2-pt;
}
// вычисляет расстояния от прямоугольника окна (rc_wnd)
// до другого прямоугольника (rc_snap), к которому исходное окно
// может притянуться.
// Здесь и далее:
// dx1 -- расстояние от левого края окна до одной из граней прямоугольника
// dx2 -- расстояние от правого края окна до одной из граней прямоугольника
// dy1 -- расстояние от верхнего края окна ...
// dy2 -- расстояние от нижнего края окна ...
static void calc_dest( const RECT rc_wnd, const RECT rc_snap, int& dx1, int& dx2, int& dy1, int& dy2 );
// вычисляет минимальные расстояния от прямоугольника окна (rc_wnd)
// до прямоугольника другого окна (rc_snap), к которому исходное окно
// может притянуться.
// Расстояния переписываются только если они больше исходных (dx1,dx2,dy1,dy2)
static void calc_min_dest( const RECT rc_wnd, const RECT rc_snap, int& dx1, int& dx2, int& dy1, int& dy2 );
// вычисляет минимальное расстояние от прямоугольника (rc_base) окна (wnd)
// до ограничевающего прямоугольника (rc_start -- обычно, это прямоугольник десктопа)
// либо до прямоуольника одного из других окон из списка s_wnd_list
static void find_nearest_dst( HWND wnd, const RECT& rc_base, const RECT& rc_start
, int& dx1, int& dx2, int& dy1, int& dy2 );
// вычисляет минимальное расстояние от прямоугольника (rc_base) окна (wnd)
// до ограничевающего прямоугольника (rc_start -- обычно, это прямоугольник десктопа)
// либо до прямоуольника одного из других окон из списка s_wnd_list
// В отличии от find_nearest_dst() применяется при поиске расстояний относительно
// мастер-окна и, также, учитывает расстояния от окон соседствующих с ним.
static void find_nearest_dst_master( HWND wnd, const RECT& rc_base, const RECT& rc_start
, int& dx1, int& dx2, int& dy1, int& dy2
, int nb_off_x, int nb_off_y );
// возвращает ограничевающий прямоугольник -- прямоугольник рабочего стола
void find_nearest_destop_rect( const RECT& rc_wnd, RECT& rc_out );
static void edge_to_anchor( int edge, DRAG_ANCHOR_X& anc_x, DRAG_ANCHOR_Y& anc_y );
static LONG& rect_side_x( RECT& rc, DRAG_ANCHOR_X anc_x );
static LONG& rect_side_y( RECT& rc, DRAG_ANCHOR_Y anc_y );
// Прямоугольники имеют соприкасающиеся грани?
static bool is_neighbours( const RECT& rc1, const RECT& rc2 );
// Окна имеют соприкасающиеся грани?
static bool is_neighbours( HWND wnd1, HWND wnd2 );
// отмечает в списке соседей данного окна.
// Перед использованием нужно очистить свойство is_neighbour всех окон
static void mark_neighbours( HWND master, container_wnd& wnd_list );
// отмечает в списке соседей данного окна.
static void recalc_neighbours( HWND master, container_wnd& wnd_list );
//static void shift_window( HWND wnd, int dx, int dy );
static void shift_neighbour_windows( HWND wnd, container_wnd& wnd_list, int dx, int dy );
static void show_neighbour_windows( HWND wnd, container_wnd& wnd_list );
static void show_all_windows( HWND wnd, container_wnd& wnd_list );
public:
//////////////////////////////////////////////////////////////////////////
// event handlers
//////////////////////////////////////////////////////////////////////////
void on_sizing( RECT* p_rc_new, int edge ); // WM_SIZING (return 1;)
void on_moving( RECT* p_rc_new ); // WM_MOVING (return 1;)
void on_enter_sizemove(); // WM_ENTERSIZEMOVE
void on_activate(); // WM_NCACTIVATE
};
#endif // __file__SNAP_HELPER_H
// snap_helper.cpp >>
#include "StdAfx.h"
#include "snap_helper.h"
snap_helper::container_wnd snap_helper::s_wnd_list;
HWND snap_helper::s_master_wnd = 0;
int snap_helper::s_thresold = 10;
bool snap_helper::is_intersect( int p1_x1, int p1_x2, int p2_x1, int p2_x2 )
{
// return true; // любопытный эффект.
return (p2_x1 <= p1_x1 && p2_x2 >= p1_x2 ) ||
//(p1_x1 <= p1_x2 && p1_x2 >= p2_x2 ) ||
pt_is_inside( p1_x1, p1_x2, p2_x1 ) ||
pt_is_inside( p1_x1, p1_x2, p2_x2 ) ||
(p2_x2 + 1 == p1_x1) ||
(p2_x1 - 1 == p1_x2);
}
void snap_helper::calc_dest( const RECT rc_wnd, const RECT rc_snap, int& dx1, int& dx2, int& dy1, int& dy2 )
{
// этот код допускает что правый край окна может притянуться
// к левому краю десктопа, верхний -- к нижнему и т.д.
// Т.о. окно становиться невидимым
/*
dx1 = nearest_dest( rc_snap.left, rc_snap.right, rc_wnd.left );
dx2 = nearest_dest( rc_snap.left, rc_snap.right, rc_wnd.right );
dy1 = nearest_dest( rc_snap.top, rc_snap.bottom, rc_wnd.top );
dy2 = nearest_dest( rc_snap.top, rc_snap.bottom, rc_wnd.bottom );
*/
dx1 = rc_snap.left - rc_wnd.left;
dx2 = rc_snap.right - rc_wnd.right;
dy1 = rc_snap.top - rc_wnd.top;
dy2 = rc_snap.bottom - rc_wnd.bottom;
}
void snap_helper::calc_min_dest( const RECT rc_wnd, const RECT rc_snap, int& dx1, int& dx2, int& dy1, int& dy2 )
{
const int& e = s_thresold;
if( is_intersect( rc_wnd.left-e, rc_wnd.right+e, rc_snap.left-e, rc_snap.right+e ) )
{
int _dy1 = nearest_dest( rc_snap.top, rc_snap.bottom, rc_wnd.top );
int _dy2 = nearest_dest( rc_snap.top, rc_snap.bottom, rc_wnd.bottom );
if( abs(_dy1) < abs(dy1) ) dy1 = _dy1;
if( abs(_dy2) < abs(dy2) ) dy2 = _dy2;
}
if( is_intersect( rc_wnd.top-e, rc_wnd.bottom+e, rc_snap.top-e, rc_snap.bottom+e ) )
{
int _dx1 = nearest_dest( rc_snap.left, rc_snap.right, rc_wnd.left );
int _dx2 = nearest_dest( rc_snap.left, rc_snap.right, rc_wnd.right );
if( abs(_dx1) < abs(dx1) ) dx1 = _dx1;
if( abs(_dx2) < abs(dx2) ) dx2 = _dx2;
}
}
void snap_helper::find_nearest_dst( HWND wnd, const RECT& rc_base, const RECT& rc_start
, int& dx1, int& dx2, int& dy1, int& dy2 )
{
RECT rc_curr;
calc_dest( rc_base, rc_start, dx1, dx2, dy1, dy2 );
container_wnd::iterator i = s_wnd_list.begin();
for( ; i != s_wnd_list.end(); i++ )
{
if( i->m_wnd == wnd ) continue;
//if( is_master && i->is_neighbour ) continue;
::GetWindowRect( i->m_wnd, &rc_curr );
calc_min_dest( rc_base, rc_curr, dx1, dx2, dy1, dy2 );
}
}
void snap_helper::find_nearest_dst_master( HWND wnd, const RECT& rc_base, const RECT& rc_start
, int& dx1, int& dx2, int& dy1, int& dy2
, int nb_off_x, int nb_off_y )
{
RECT rc_curr;
calc_dest( rc_base, rc_start, dx1, dx2, dy1, dy2 );
container_wnd::iterator i = s_wnd_list.begin();
for( ; i != s_wnd_list.end(); i++ )
{
if( i->m_wnd == wnd ) continue;
if( i->m_is_neighbour )
{
container_wnd::iterator j = s_wnd_list.begin();
RECT rc_neighbour;
::GetWindowRect( i->m_wnd, &rc_neighbour );
::OffsetRect( &rc_neighbour, nb_off_x, nb_off_y );
calc_min_dest( rc_neighbour, rc_start, dx1, dx2, dy1, dy2 );
for( ; j != s_wnd_list.end(); j++ )
{
if( j->m_wnd == wnd ) continue;
if( j->m_wnd == i->m_wnd ) continue;
if( j->m_is_neighbour ) continue;
::GetWindowRect( j->m_wnd, &rc_curr );
calc_min_dest( rc_neighbour, rc_curr, dx1, dx2, dy1, dy2 );
}
}
else
{
::GetWindowRect( i->m_wnd, &rc_curr );
calc_min_dest( rc_base, rc_curr, dx1, dx2, dy1, dy2 );
}
}
}
void snap_helper::find_nearest_destop_rect( const RECT& rc_wnd, RECT& rc_out )
{
HMONITOR hMonitor = MonitorFromRect( &rc_wnd, MONITOR_DEFAULTTONEAREST );
MONITORINFO mi; mi.cbSize = sizeof( MONITORINFO );
GetMonitorInfo( hMonitor, &mi );
rc_out = mi.rcWork;
//find_nearest_rect( rc_wnd, mi.rcWork, mi.rcMonitor, rc_out );
}
void snap_helper::edge_to_anchor( int edge, DRAG_ANCHOR_X& anc_x, DRAG_ANCHOR_Y& anc_y )
{
anc_x = DAX_UNKNOWN;
anc_y = DAY_UNKNOWN;
if( edge == WMSZ_LEFT || edge == WMSZ_TOPLEFT || edge == WMSZ_BOTTOMLEFT ) anc_x = DAX_LEFT;
else if( edge == WMSZ_RIGHT || edge == WMSZ_TOPRIGHT || edge == WMSZ_BOTTOMRIGHT ) anc_x = DAX_RIGHT;
if( edge == WMSZ_TOP || edge == WMSZ_TOPLEFT || edge == WMSZ_TOPRIGHT ) anc_y = DAY_TOP;
else if( edge == WMSZ_BOTTOM || edge == WMSZ_BOTTOMLEFT || edge == WMSZ_BOTTOMRIGHT ) anc_y = DAY_BOTTOM;
}
LONG& snap_helper::rect_side_x( RECT& rc, DRAG_ANCHOR_X anc_x )
{
if( anc_x == DAX_RIGHT ) return rc.right;
return rc.left;
}
LONG& snap_helper::rect_side_y( RECT& rc, DRAG_ANCHOR_Y anc_y )
{
if( anc_y == DAY_BOTTOM ) return rc.bottom;
return rc.top;
}
bool snap_helper::is_neighbours( const RECT& rc1, const RECT& rc2 )
{
if( is_intersect( rc1.left, rc1.right, rc2.left, rc2.right ) )
{
if( rc1.top == rc2.top || rc1.top == rc2.bottom+0 ||
rc1.bottom == rc2.bottom || rc1.bottom == rc2.top-0 ) return true;
}
if( is_intersect( rc1.top, rc1.bottom, rc2.top, rc2.bottom ) )
{
if( rc1.left == rc2.left || rc1.left == rc2.right+0 ||
rc1.right == rc2.right || rc1.right == rc2.left-0 ) return true;
}
return false;
}
bool snap_helper::is_neighbours( HWND wnd1, HWND wnd2 )
{
RECT rc1, rc2;
::GetWindowRect( wnd1, &rc1 );
::GetWindowRect( wnd2, &rc2 );
return is_neighbours( rc1, rc2 );
}
void snap_helper::mark_neighbours( HWND master, container_wnd& wnd_list )
{
container_wnd::iterator i = wnd_list.begin();
for( ; i != wnd_list.end(); i++ )
{
if( i->m_wnd == master ) continue;
if( !i->m_is_neighbour && is_neighbours( i->m_wnd, master ) )
{
i->m_is_neighbour = true;
mark_neighbours( i->m_wnd, wnd_list );
}
}
}
void snap_helper::recalc_neighbours( HWND master, container_wnd& wnd_list )
{
container_wnd::iterator i = wnd_list.begin();
for( ; i != wnd_list.end(); i++ )
{
i->m_is_neighbour = false;
}
mark_neighbours( master, wnd_list );
}
/*
void snap_helper::shift_window( HWND wnd, int dx, int dy )
{
RECT rc;
::GetWindowRect( wnd, &rc );
::OffsetRect( &rc, dx, dy );
::MoveWindow( wnd, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, TRUE );
}
*/
void snap_helper::shift_neighbour_windows( HWND wnd, container_wnd& wnd_list, int dx, int dy )
{
container_wnd::iterator i = wnd_list.begin();
int n_wnd = 0;
/*
for( ; i != wnd_list.end(); i++ )
{
if( i->m_wnd == wnd ) continue;
if( i->m_is_neighbour )
shift_window( i->m_wnd, dx, dy );
}
return;
*/
for( ; i != wnd_list.end(); i++ )
{
if( i->m_is_neighbour || i->m_wnd == wnd ) n_wnd++;
}
HDWP h_dwp = ::BeginDeferWindowPos( n_wnd );
i = wnd_list.begin();
for( ; i != wnd_list.end(); i++ )
{
if( i->m_is_neighbour || i->m_wnd == wnd )
{
RECT rc;
::GetWindowRect( i->m_wnd, &rc );
::DeferWindowPos( h_dwp, i->m_wnd, 0, rc.left + dx, rc.top +dy, 0, 0, SWP_NOSIZE );
}
}
::EndDeferWindowPos( h_dwp );
}
void snap_helper::show_neighbour_windows( HWND wnd, container_wnd& wnd_list )
{
container_wnd::iterator i = wnd_list.begin();
for( ; i != wnd_list.end(); i++ )
{
if( i->m_wnd == wnd ) continue;
if( i->m_is_neighbour )
{
::SetWindowPos( i->m_wnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
}
}
}
void snap_helper::show_all_windows( HWND wnd, container_wnd& wnd_list )
{
container_wnd::iterator i = wnd_list.begin();
for( ; i != wnd_list.end(); i++ )
{
if( i->m_wnd == wnd ) continue;
::SetWindowPos( i->m_wnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
}
}
//////////////////////////////////////////////////////////////////////////
void snap_helper::on_sizing( RECT* p_rc_new, int edge )
{
edge_to_anchor( edge, m_drag_anchor_x, m_drag_anchor_y );
LONG& side_x = rect_side_x( *p_rc_new, m_drag_anchor_x );
LONG& side_y = rect_side_y( *p_rc_new, m_drag_anchor_y );
side_x += m_drag_err_x;
side_y += m_drag_err_y;
RECT rc_nearest;
find_nearest_destop_rect( *p_rc_new, rc_nearest );
int dx1, dx2, dy1, dy2;
find_nearest_dst( m_wnd, *p_rc_new, rc_nearest, dx1, dx2, dy1, dy2 );
int dx = 0;
int dy = 0;
switch( m_drag_anchor_x )
{
default: break;
case DAX_LEFT:
dx = is_close(dx1) ? dx1 : 0; break;
case DAX_RIGHT:
dx = is_close(dx2) ? dx2 : 0; break;
}
switch( m_drag_anchor_y )
{
default: break;
case DAY_TOP:
dy = is_close(dy1) ? dy1 : 0; break;
case DAY_BOTTOM:
dy = is_close(dy2) ? dy2 : 0; break;
}
side_x += dx;
side_y += dy;
m_drag_err_x = dx;
m_drag_err_y = dy;
}
void snap_helper::on_moving( RECT* p_rc_new )
{
::OffsetRect( p_rc_new, m_drag_err_x, m_drag_err_y );
RECT rc_curr;
::GetWindowRect( m_wnd, &rc_curr );
int dx = p_rc_new->left - rc_curr.left;
int dy = p_rc_new->top - rc_curr.top;
int dx1, dx2, dy1, dy2;
RECT rc_nearest;
find_nearest_destop_rect( *p_rc_new, rc_nearest );
if( is_master() )
find_nearest_dst_master( m_wnd, *p_rc_new, rc_nearest, dx1, dx2, dy1, dy2, dx, dy );
else
find_nearest_dst( m_wnd, *p_rc_new, rc_nearest, dx1, dx2, dy1, dy2 );
dx = abs(dx1) < abs(dx2) ? dx1 : dx2;
dy = abs(dy1) < abs(dy2) ? dy1 : dy2;
dx = is_close(dx) ? dx : 0;
dy = is_close(dy) ? dy : 0;
::OffsetRect( p_rc_new, dx, dy );
if( is_master() )
{
shift_neighbour_windows( m_wnd, s_wnd_list, p_rc_new->left-rc_curr.left, p_rc_new->top-rc_curr.top );
}
m_drag_err_x = -dx;
m_drag_err_y = -dy;
}
void snap_helper::on_enter_sizemove()
{
recalc_neighbours( m_wnd, s_wnd_list );
show_neighbour_windows( m_wnd, s_wnd_list );
m_drag_anchor_x = DAX_UNKNOWN;
m_drag_anchor_y = DAY_UNKNOWN;
m_drag_err_x = 0;
m_drag_err_y = 0;
}
void snap_helper::on_activate()
{
recalc_neighbours( m_wnd, s_wnd_list );
//show_neighbour_windows( m_wnd, s_wnd_list );
show_all_windows( m_wnd, s_wnd_list );
}