button表示の雛形(エフェクト表示、非矩形表示等の検討用)

古臭いなぁとどうしても感じてしまうのがWin32APIのボタン表示。(その他にもあるけど)
これをWin32APIだけで解決できるように、ボタンウィンドウ(とそのツールチップ)の動作再現コードを
いろいろかき集めて構成してみました。
とりあえずシェード表示だけできればいいだろ、ってことで、ボタンウィンドウはuxthemeのBufferdPaintの利用でウィンドウクラス登録、ツールチップはCreateWindowしてAnimateWindowでShow/Hide、としています。GDI仕様。。。

追記:) ツールチップにシャドウがつくようにCS_DROPSHADOWを追加しました。

```
static const char button_wc[] = "MyBUTTON";
static const wchar_t button_tc[] = L"BUTTON";
static int extra_offset;
static WNDPROC orig_button_proc=NULL;
using button_t=
struct{
  HTHEME theme;
  DWORD style      : 28;
  DWORD hide_accel :  1;
  DWORD hide_focus :  1;
  DWORD no_redraw  :  1;
  DWORD laststate  :  8;
  HWND  hTP;
  DWORD bTrack     :  1;
  DWORD bHoverDisp :  1;
};

int button_init_module(void)
{
  WNDCLASS wc;
  GetClassInfo(NULL, "BUTTON", &wc);

  orig_button_proc=wc.lpfnWndProc;
  extra_offset=wc.cbWndExtra;
  wc.lpfnWndProc=button_proc;
  wc.cbWndExtra+=sizeof(button_t*);
  wc.style |= CS_GLOBALCLASS;
  wc.hInstance = NULL;
  wc.lpszClassName = button_wc;
  if(!RegisterClass(&wc)) return -1;
  BufferedPaintInit(); 
  return 0;
}

void button_fini_module(void)
{
  BufferedPaintUnInit();
  UnregisterClass(button_wc, NULL);
}

static void button_paint(HWND hbutton, button_t* button, HDC hdc, int state)
{
  if(button->no_redraw) return;

  RECT rect;
  GetClientRect(hbutton, &rect);

  HFONT old_hfont;
  int old_bk_mode;
  COLORREF old_text_color;
  HRGN old_rgn;
  
  HFONT hfont;
  hfont=(HFONT)SendMessage(hbutton, WM_GETFONT, 0, 0); if(hfont==NULL) hfont=(HFONT)GetStockObject(SYSTEM_FONT);
  old_hfont=(HFONT)SelectObject(hdc, hfont);
  old_bk_mode=GetBkMode(hdc);
  old_text_color=GetTextColor(hdc);
  old_rgn=CreateRectRgn(0, 0, 0, 0); if(GetClipRgn(hdc, old_rgn)!=1){ DeleteObject(old_rgn); old_rgn=NULL; }

  if(!button->theme && (button->style & BS_DEFPUSHBUTTON)){
    SelectObject(hdc, GetSysColorBrush(COLOR_WINDOWFRAME));
    Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
    rect.left+=1; rect.top+=1; rect.right-=1; rect.bottom-=1;
  }
  
  if(button->theme){
    if(IsThemeBackgroundPartiallyTransparent(button->theme, BP_PUSHBUTTON, state)) DrawThemeParentBackground(hbutton, hdc, &rect);
    RECT content;
    GetThemeBackgroundContentRect(button->theme, hdc, BP_PUSHBUTTON, state, &rect, &content);
    //    IntersectClipRect(hdc, content.left, content.top, content.right, content.bottom);
    DrawThemeBackground(button->theme, hdc, BP_PUSHBUTTON, state, &rect, &rect);
    IntersectClipRect(hdc, content.left, content.top, content.right, content.bottom);
  }else{
    HWND parent=GetAncestor(hbutton, GA_PARENT);
    if(parent==NULL) parent=hbutton;
    HBRUSH brush=(HBRUSH)SendMessage(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hbutton);
    if(brush==NULL) brush=(HBRUSH)DefWindowProc(parent, WM_CTLCOLORBTN, (WPARAM)hdc, (LPARAM)hbutton);
    
    IntersectClipRect(hdc, rect.left, rect.top, rect.right, rect.bottom);
    DrawFrameControl(hdc, &rect, DFC_BUTTON, DFCS_BUTTONPUSH | state);
    if(state==DFCS_PUSHED) rect.left+=1; rect.top+=1; rect.right+=1; rect.bottom+=1;
    rect.bottom-=2;
  }

  if((SendMessage(hbutton, BM_GETSTATE, 0, 0) & BST_FOCUS) && !button->hide_focus){
    SelectClipRgn(hdc, NULL);
    if(button->theme){
      DrawFocusRect(hdc, &rect);
    }else{
      rect.left+=1; rect.top+=2; rect.right-=1; rect.bottom-=2;
      DrawFocusRect(hdc, &rect);
      rect.left+=1; rect.top+=1; rect.right-=1; rect.bottom-=1;
    }
  }

  int flags=0;
  if(button->style & BS_ICON){
      HICON hicon=(HICON)SendMessage(hbutton, BM_GETIMAGE, IMAGE_ICON, 0);
    if(hicon!=NULL){
      flags=DST_ICON;

      ICONINFO ii;
      GetIconInfo(hicon, &ii);
      BITMAP bmp;
      SIZE size;
      GetObject(ii.hbmMask, sizeof(BITMAP), &bmp);
      size.cx=bmp.bmWidth; size.cy=bmp.bmHeight;
      if(ii.hbmColor==NULL){ size.cy/=2; }else{ DeleteObject(ii.hbmColor); }
      DeleteObject(ii.hbmMask);
      if(button->style & WS_DISABLED) flags|=DSS_DISABLED;
      DrawState(hdc, NULL, NULL, (LPARAM)hicon, 0, (rect.right+rect.left-size.cx)/2, (rect.bottom+rect.top-size.cy)/2,
                size.cx, size.cy, flags);
    }
  }else{
    flags=DT_VCENTER;
    switch(button->style & (BS_TOP|BS_BOTTOM)){
    case BS_TOP:
      flags|=DT_TOP;
      break;
    case BS_BOTTOM:
      flags|=DT_BOTTOM;
      break;
    }
    switch(button->style & (BS_LEFT|BS_CENTER|BS_RIGHT)){
    case BS_LEFT:
      flags|=DT_LEFT;
      break;
    case BS_RIGHT:
      flags|=DT_RIGHT;
      break;
    default:
      if(GetWindowLong(hbutton, GWL_EXSTYLE) & WS_EX_RIGHT)
        flags|=DT_RIGHT;
      else
        flags|=DT_CENTER;
      break;
    }
    flags|=((button->style & BS_MULTILINE) ? DT_WORDBREAK : DT_SINGLELINE);
    if(button->hide_accel) flags|=DT_HIDEPREFIX;
    
    char buffer[256];
    int n=SendMessage(hbutton, WM_GETTEXT, 255, (LPARAM)buffer);
    if(button->theme){
      wchar_t wbuffer[256]; MultiByteToWideChar(CP_ACP, 0, buffer, -1, wbuffer, 256);
      DrawThemeText(button->theme, hdc, BP_PUSHBUTTON, state, wbuffer, n, flags, 0, &rect);
    }else{
      SetBkMode(hdc, TRANSPARENT);
      SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT));
      DrawText(hdc, buffer, n, &rect, flags);
    }
  }

  SelectObject(hdc, old_hfont);
  SetBkMode(hdc, old_bk_mode);
  SetTextColor(hdc, old_text_color);
  SelectClipRgn(hdc, old_rgn); if(old_rgn!=NULL) DeleteObject(old_rgn);
}

LRESULT CALLBACK button_proc(HWND hbutton, UINT msg, WPARAM wparam, LPARAM lparam)
{
  button_t *button=(button_t*) GetWindowLongPtr(hbutton, extra_offset);
  switch(msg){
  
  case WM_MOUSEMOVE:
    {
      if((button->bTrack==false)&&(button->bHoverDisp==false)){
        TRACKMOUSEEVENT tme={sizeof(TRACKMOUSEEVENT), TME_HOVER|TME_LEAVE, hbutton, 500 };
        TrackMouseEvent(&tme);
        button->bTrack=true;
      }
      button->bHoverDisp=false;
    }
    break;
  case WM_MOUSEHOVER:
    {
      button->bTrack=true;
      if(button->bHoverDisp==false){
        POINT pt={LOWORD(lparam), HIWORD(lparam)};
        RECT rc; GetWindowRect(hbutton, &rc);
        MapWindowPoints(hbutton, NULL, &pt, 1); // num of points=1
        if(PtInRect(&rc, pt)){
          button->bHoverDisp=1;
          button->hTP=CreateWindow("STATIC", "tooltip",
                                   WS_POPUP|WS_BORDER, pt.x+10, pt.y+10, 100, 30,
                                   hbutton, NULL, hInst, NULL);
          DWORD style=GetClassLongPtr(button->hTP, GCL_STYLE);
          SetClassLongPtr(button->hTP, GCL_STYLE, style|CS_DROPSHADOW);
          HFONT hfont=(HFONT)SendMessage(hbutton, WM_GETFONT, 0, 0);
          SendMessage(button->hTP, WM_SETFONT, (WPARAM)hfont, 0);
          AnimateWindow(button->hTP, 250, AW_BLEND);
          TRACKMOUSEEVENT tme={sizeof(TRACKMOUSEEVENT), TME_LEAVE, hbutton, 100};
          button->bTrack=TrackMouseEvent(&tme);
        }
      }
    }
    break;
  case WM_MOUSELEAVE:
    {
      button->bTrack=button->bHoverDisp=false;
      if(button->hTP){ AnimateWindow(button->hTP, 250, AW_BLEND|AW_HIDE); DestroyWindow(button->hTP); button->hTP=NULL; }
    }
    break;

  case WM_PAINT:
  case WM_PRINTCLIENT:
    {
      if(!button->theme) break;

      HDC hdc;
      PAINTSTRUCT ps;
      if(msg==WM_PAINT) hdc=BeginPaint(hbutton, &ps); else hdc=(HDC)wparam;
      if(BufferedPaintRenderAnimation(hbutton, hdc)==TRUE){
        // If this function returns TRUE, the application should do no further painting.
        // If this function returns FALSE, the application should paint normally.        
        if(msg==WM_PAINT) EndPaint(hbutton, &ps);
        break;
      }else{
        int state=PBS_NORMAL;
        if(button->style & WS_DISABLED){
          state=PBS_DISABLED;
        }else{
          LRESULT s=SendMessage(hbutton, BM_GETSTATE, 0, 0);
          if(s & BST_PUSHED)   state=PBS_PRESSED;
          else if(s & BST_HOT) state=PBS_HOT;
          else if(button->style & BS_DEFPUSHBUTTON) state=PBS_DEFAULTED;
        }
        if(button->laststate!=state){
          BP_ANIMATIONPARAMS animParams={};
          animParams.cbSize=sizeof(BP_ANIMATIONPARAMS);
          animParams.style=BPAS_LINEAR;
          animParams.dwDuration=250;
          RECT rc;
          GetClientRect(hbutton, &rc);
          HDC hdcFrom, hdcTo;
          HANIMATIONBUFFER hbpAnim=BeginBufferedAnimation(hbutton, hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &animParams, &hdcFrom, &hdcTo);
          if(hbpAnim){
            if(hdcFrom!=NULL) button_paint(hbutton, button, hdcFrom, button->laststate); // render hdcFrom
            if(hdcTo!=NULL)   button_paint(hbutton, button, hdcTo,   state);             // render hdcTo
            EndBufferedAnimation(hbpAnim, TRUE);                                         // start auto-animation
            // If TRUE, updates the target DC with the animation.
            // If FALSE, the animation is not started, the target DC is not updated, and the hbpAnimation parameter is freed.            
          }
          button->laststate=state;
        }else{
          button_paint(hbutton, button, hdc, state); // 追記
        }
      }
      if(msg==WM_PAINT) EndPaint(hbutton, &ps);
      return 0;
    }
    break;
      
  case WM_UPDATEUISTATE:
    {
      LRESULT ret=CallWindowProc(orig_button_proc, hbutton, WM_UPDATEUISTATE, wparam, lparam);
      DWORD flags=SendMessage(hbutton, WM_QUERYUISTATE, 0, 0);
      button->hide_focus=(flags&UISF_HIDEFOCUS) ? 1 : 0;
      button->hide_accel=(flags&UISF_HIDEACCEL) ? 1 : 0;
      if(!button->no_redraw) InvalidateRect(hbutton, NULL, FALSE);
      return ret;
    }
    break;
    
  case WM_LBUTTONDOWN:
    {
    }
    break;
    
  case WM_LBUTTONDBLCLK:
    {
    }
    break;
    
  case WM_SETREDRAW:
    {
      button->no_redraw=(!wparam);
    }
    break;
    
  case WM_STYLECHANGED:
    {
      if(wparam==GWL_STYLE){
        STYLESTRUCT* ss=(STYLESTRUCT*)lparam;
        button->style=ss->styleNew;
      }
    }
    break;
    
  case WM_THEMECHANGED:
    {
      if(button->theme) CloseThemeData(button->theme);
      button->theme=OpenThemeData(hbutton, button_tc);
      InvalidateRect(hbutton, NULL, FALSE);
    }
    break;
    
  case WM_SYSCOLORCHANGE:
    {
      InvalidateRect(hbutton, NULL, FALSE);
    }
    break;
    
  case WM_NCCREATE:
    {
      if(!CallWindowProc(orig_button_proc, hbutton, WM_NCCREATE, wparam, lparam)) return FALSE;
      button=(button_t *)malloc(sizeof(button_t));
      memset(button, 0, sizeof(button_t));
      button->style=((CREATESTRUCT*)lparam)->style;
      SetWindowLongPtr(hbutton, extra_offset, (LONG_PTR)button);
      return TRUE;
    }
    break;
    
  case WM_CREATE:
    {
      if(CallWindowProc(orig_button_proc, hbutton, WM_CREATE, wparam, lparam)!=0) return -1;
      button->theme=OpenThemeData(hbutton, button_tc);
      DWORD ui_state=SendMessage(hbutton, WM_QUERYUISTATE, 0, 0);
      button->hide_focus=(ui_state & UISF_HIDEFOCUS) ? 1 : 0;
      button->hide_accel=(ui_state & UISF_HIDEACCEL) ? 1 : 0;
      return 0;
    }
    break;
    
  case WM_DESTROY:
    {
      if(button->theme){
        CloseThemeData(button->theme);
        button->theme=NULL;
      }
    }
    break;
    
  case WM_NCDESTROY:
    {
      if(button) free(button);
    }
    break;
  }
  return CallWindowProc(orig_button_proc, hbutton, msg, wparam, lparam);
}
```