;Пример использования сопроцессора. Команды FPU используются в процедуре CalcXY,
; работу которой желательно посмотреть под отладчиком (с.м также Глава #8 - 
;Отладка). Окно стэка сопроцессора отображается командой wf.
;Также, данный пример практически дополняет предыдущую главу, показывая 
;программирование графики, используя GDI. 
;
;Программа - часы. Можно двигать удерживая левую кнопку мыши, 
;выход - щелчек правой. Алгоритм работы более-менее стандартный:
;После создания окна и т.д установим таймер, который будет посылать сообщения WM_TIMER с заданым интервалом. Обработчик сообщения объявит недействительным содержимое окна, и WM_PAINT нарисует часы.
;как всегда, программа максимально проста, чтбы было легче в ней разобраться. 
  .486
  .model flat,stdcall
  option casemap :none   ; case sensitive
;используем inc и lib файлы из пакета masm32 (с.м главу #8)
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
includelib c:\masm32\lib\kernel32.lib
include c:\masm32\include\user32.inc
includelib c:\masm32\lib\user32.lib
include c:\masm32\include\gdi32.inc
includelib c:\masm32\lib\gdi32.lib

;Константы для настройки программы
ClockX=61
ClockY=61
ClockColor=0FFFFFFh
BorderColor=0903030h
HourColor=0FF4040h
MinuteColor=0FFh
SecondColor=0
;Разница радиуса с максимальным
HourRadius=15
MinuteRadius=12
SecondRadius=5

 .data
ColorArray dd HourColor,MinuteColor,SecondColor
RadiusArray dd HourRadius,MinuteRadius,SecondRadius

ProgramClassName db 'SmallClock',0 ;класс основонго окошка
WinTitle db 'Clock',0
WClass WNDCLASSEX 
MsgS MSG<>
DragCords dd ?
 .code
;Процедура для вычисления координат точки на окружности, 
; длинной MaxVal и определяемой прямоугольником EllipseRect
;Алгоритм работы:
; RadsLen=CurrentVal/MaxVal*2*PI
; XSize=EllppseRect.right-EllppseRect.left-1
; X=EllipseRect.left+XSize/2*(1+sin(RadsLen))
; YSize=EllipseRect.bottom-EllipseRect.top-1
; Y=EllipseRect.top+YSize/2*(1-cos(RadsLen))
;out: eax=x; edx=y
DivideBy2 real4 0.5 ;заменим деление на 2 умножением на 0.5.
 ;Это занчительно быстрее. Об оптимизации будет рассказано позже.
CalcXY proc EllipseRect:ptr RECT,CurrentVal:DWORD,MaxVal:DWORD
 mov ecx,[EllipseRect]
 assume ecx:ptr RECT

 fild [CurrentVal]
 fidiv dword ptr [MaxVal]
 fldpi
 fmulp st(1),st(0)
 fadd st(0),st(0)  ;st(0)=RadsLen
 fsincos

 fild [ecx].bottom
 fisub [ecx].top
 fld1
 fsub st(1),st(0)
 fsubrp st(2),st(0)
 fmulp st(1),st(0)
 fmul [DivideBy2]
 push edx
 fistp dword ptr [esp]
 pop edx
 add edx,[ecx].top  ;edx=y

 fild [ecx].right
 fisub [ecx].left
 fld1
 fsub st(1),st(0)
 faddp st(2),st(0)
 fmulp st(1),st(0)
 fmul [DivideBy2]
 push eax
 fistp dword ptr [esp]
 pop eax
 add eax,[ecx].left ;eax=x

 assume ecx:nothing
 ret
CalcXY endp

;Макрос для уменьшения RECT'а со всех сторон.
;параметры: Rect - указатель на структуру RECT
;   SubValue - значение, на которое нужно уменьшить RECT
SubRect macro Rect,SubValue
 add [Rect].left,SubValue
 add [Rect].top,SubValue
 sub [Rect].right,SubValue
 sub [Rect].bottom,SubValue
endm

start:
;зарегистрируем класс главного окна (см главу #7)

 mov edi,offset WClass
 assume edi:ptr WNDCLASSEX

 invoke GetModuleHandle,0
 mov [edi].hInstance,eax
 invoke LoadIcon,0,IDI_APPLICATION
 mov [edi].hIcon,eax
 mov [edi].hIconSm,eax
 invoke LoadCursor,0,IDC_ARROW
 mov [edi].hCursor,eax
 assume edi:nothing

 invoke RegisterClassEx,edi

;Получим размер экрана для расположения окна по центру.
 invoke GetSystemMetrics,SM_CXSCREEN
 sub eax,ClockX
 shr eax,1
 xchg eax,ebx
 invoke GetSystemMetrics,SM_CYSCREEN
 sub eax,ClockY
 shr eax,1
;создадим главное окно
 invoke CreateWindowEx,WS_EX_TOOLWINDOW or WS_EX_TOPMOST,\
  offset ProgramClassName,offset WinTitle,\
  WS_VISIBLE or WS_POPUP,ebx,eax,ClockX,ClockY,\
  0,0,[WClass.hInstance],0
 xchg eax,ebx
;установим таймер для периодической перерисовки часов
 invoke SetTimer,ebx,0,200,0
;создадим и установим регион для окна, чтобы окно было круглой формы.
 invoke CreateEllipticRgn,-1,-1,ClockX+2,ClockY+2
 invoke SetWindowRgn,ebx,eax,1

MsgLoop:
 invoke GetMessage,offset MsgS,0,0,0
 inc eax
 jz EndMsgLoop
 dec eax
 jz EndMsgLoop
 invoke TranslateMessage,offset MsgS
 invoke DispatchMessage,offset MsgS
 jmp MsgLoop
EndMsgLoop:
 invoke ExitProcess,0

MainWindowProc proc hwnd:dword,uMsg:dword,wParam:dword,lParam:dword
local ps:PAINTSTRUCT
local ClientRect:RECT
local MemDC:dword
local CurrentTime:SYSTEMTIME

 cmp [uMsg],WM_PAINT
 jnz NotWmPaint
;обработчик WM_PAINT
 invoke GetLocalTime,addr CurrentTime
 invoke GetClientRect,[hwnd],addr ClientRect

 invoke BeginPaint,[hwnd],addr ps
;создадим MemDC
 invoke CreateCompatibleDC,ps.hdc
 mov [MemDC],eax
 invoke CreateCompatibleBitmap,ps.hdc,ClientRect.right,ClientRect.bottom
 invoke SelectObject,[MemDC],eax
 push eax    ;хэндл предыдущего Bitmap

;нарисуем пустой циферблат
 invoke CreateSolidBrush,ClockColor
 invoke SelectObject,[MemDC],eax
 push eax    ;хэндл предыдущего Brush
 invoke CreatePen,PS_SOLID,3,BorderColor
 invoke SelectObject,[MemDC],eax
 push eax   ;хэндл предыдущего Pen
 invoke Ellipse,[MemDC],0,0,ClientRect.right,ClientRect.bottom

;цикл рисования стрелок
 push ebx
 xor ebx,ebx
DrawClockLines:
 mov eax,ebx
 xor al,11b
 invoke CreatePen,PS_SOLID,eax,dword ptr [ColorArray+ebx*4]
 invoke SelectObject,[MemDC],eax
 invoke DeleteObject,eax

 invoke GetClientRect,[hwnd],addr ClientRect
 mov eax,ClientRect.right
 shr eax,1
 mov ecx,ClientRect.bottom
 shr ecx,1
 invoke MoveToEx,[MemDC],eax,ecx,0
 mov eax,dword ptr [RadiusArray+ebx*4]
 SubRect ClientRect,eax

 movzx eax,word ptr [CurrentTime.wHour+ebx*2]
 or ebx,ebx
 jnz NotDrawHour
 lea ecx,[eax*4+eax]  ;*5
 movzx eax,word ptr [CurrentTime.wMinute]
 cdq
 push 12
 div dword ptr [esp]
 pop edx
 add eax,ecx
NotDrawHour:
 invoke CalcXY,addr ClientRect,eax,60
 invoke LineTo,[MemDC],eax,edx
 inc ebx
 cmp bl,3
 jnz DrawClockLines
 pop ebx

 invoke GetClientRect,[hwnd],addr ClientRect
 invoke CreatePen,PS_SOLID,1,BorderColor
 invoke SelectObject,[MemDC],eax
 invoke DeleteObject,eax

;нарисуем метки времени (каждые 5 минут)
 mov ecx,12
DrawTimeLabels:
 push ecx
 SubRect ClientRect,4
 invoke CalcXY,addr ClientRect,dword ptr [esp+4],12
 invoke MoveToEx,[MemDC],eax,edx,0
 SubRect ClientRect,-4
 invoke CalcXY,addr ClientRect,dword ptr [esp+4],12
 invoke LineTo,[MemDC],eax,edx
 pop ecx
 loop DrawTimeLabels

;скопируем MemDC в DC окна
 invoke BitBlt,ps.hdc,0,0,ClientRect.right,ClientRect.bottom,\
  [MemDC],0,0,SRCCOPY

;удалим созданные объекты
 push [MemDC]
 call SelectObject  ;2 pushed
 invoke DeleteObject,eax
 push [MemDC]
 call SelectObject  ;2 pushed
 invoke DeleteObject,eax
 push [MemDC]
 call SelectObject  ;2 pushed
 invoke DeleteObject,eax

 invoke DeleteDC,[MemDC]
 invoke EndPaint,[hwnd],addr ps
 xor eax,eax
 ret

NotWmPaint:
 cmp [uMsg],WM_TIMER
 jnz NotWmTimer
;обработчик таймера. Все окно в Update Region
 invoke InvalidateRect,[hwnd],0,0
 xor eax,eax
 ret
NotWmTimer:

;до WM_DESTROY - обработка сообщений от мыши
 cmp [uMsg],WM_LBUTTONDOWN
 jnz NotWmLButtonDown
 mov eax,[lParam]
 mov [DragCords],eax
 invoke SetCapture,[hwnd]
 jmp CallDefProc
NotWmLButtonDown:

 cmp [uMsg],WM_LBUTTONUP
 jnz NotWmLButtonUp
 invoke ReleaseCapture
 jmp CallDefProc
NotWmLButtonUp:

 cmp [uMsg],WM_MOUSEMOVE
 jnz NotWmMouseMove
 cmp [wParam],MK_LBUTTON
 jnz NotWmMouseMove
 push eax
 push eax
 invoke GetCursorPos,esp
 pop eax
 movzx ecx,word ptr [DragCords]
 sub eax,ecx
 pop edx
 movzx ecx,word ptr [DragCords+2]
 sub edx,ecx
 invoke SetWindowPos,[hwnd],0,eax,edx,0,0,SWP_NOSIZE
 jmp CallDefProc
NotWmMouseMove:
 
 cmp [uMsg],WM_RBUTTONUP
 jz ExitFromClock

;стандартная часть процедуры обработки сообщений с.м главу #7.
 cmp [uMsg],WM_DESTROY
 jnz NotWmDestroy
ExitFromClock:
 invoke PostQuitMessage,0
 xor eax,eax
 ret
NotWmDestroy:
CallDefProc:
 invoke DefWindowProc,[hwnd],[uMsg],[wParam],[lParam]
 ret

MainWindowProc endp
end start
