April 19, 2024, 01:49:34 AM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Animation of user interface elements

Started by fasecero, November 12, 2018, 10:25:07 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

fasecero

November 12, 2018, 10:25:07 PM Last Edit: November 17, 2018, 08:43:00 PM by fasecero
Hello guys. This is just an empty announcement for now. I'm making an attempt to bring some animations to our static gui world. First I tried to do this by using gdi/gdiplus, timers, and math. But after adding more than a few objects the code gets so complicated that it is almost impossible to follow. Also gdi/gdi+ raises the cpu usage quite a bit on complex designs.

So I downloaded the windows 7 sdk and after studying it I made a wrapper around some interesting stuff that I found in there. Unfortunately these APIS (very complex BTW, full of nasty templates and COM interfaces) are not included in Sapero's include files, so I had to make a wrapper with c.

The DLL will allow IWBASIC to implement some aditional windows technologies involving animation:

Quote- Windows Animation Manager (enables rich animation of user interface elements)
- Direct 2D (gpu hardware-accelerated, 2-D graphics API for 2-D geometry, bitmaps, and text)
- Media Fundation (audio and video playback, directshow replacement)
- Gif Support (animated gif standard playback)

If you have windows 10, when you click on the Start Menu you can see how the window goes from bottom to top and then some shortcuts make additional animations (rotation, slides, etc), this menu is implemented by Windows using Direct2D + Windows Animation Manager :)

Comming soon. Currently I'm doing some samples, testing to remove any potential memory leaks and stuff.

MSDN references
QuoteWindows Animation:  https://docs.microsoft.com/es-es/windows/desktop/UIAnimation/scenic-animation-api-overview
Direct2D:                  https://docs.microsoft.com/en-us/windows/desktop/direct2d/direct2d-portal
Media Fundation:       https://docs.microsoft.com/en-us/windows/desktop/medfound/about-the-media-foundation-sdk

Brian

Has Sapero changed his name to Fasecero! Good stuff, Gabriel

Brian

fasecero

I wish lol. He was like a god-like human being. I've learned so much thanks to him! Nah, he was completely on another level, it's like spending your whole life studying chess and then you play against one of those child genius who are just starting and they beat you up without even looking at the board  ;D

jalih

Quote from: fasecero on November 12, 2018, 10:25:07 PM
So I downloaded the windows 7 sdk and after studying it I made a wrapper around some interesting stuff that I found in there. Unfortunately these APIS (very complex BTW, full of nasty templates and COM interfaces) are not included in Sapero's include files, so I had to make a wrapper with c.

A while ago I tried using some Direct 2D stuff with MiniBASIC compiler. I made some progress and was able to compile minimal working application using Direct 2D. It was really slow and tedious process. Inside header files a lot of stuff is done using macros and these too have to be converted for the basic compiler along with type definitions and imports.

fasecero

QuoteA while ago I tried using some Direct 2D stuff with MiniBASIC compiler. I made some progress and was able to compile minimal working application using Direct 2D. It was really slow and tedious process. Inside header files a lot of stuff is done using macros and these too have to be converted for the basic compiler along with type definitions and imports.

You're totally right there. While I have tried to encapsulate everything I could, there is still work to do on both sides C/IWB. I'm currently working on a small example where you can choose an image over several availables on the screen while these are moving along with a small animated text and the code is making me to reconsider the dll design much more than I expected. So probably it will take several days.

The release will be more or less like this:
- zip with compiled content only (animation.dll & animation.inc) attached on first post
- new post with 2 examples (media fundation & animated gif) / these are done
- new post with 1 example (win anim manager tutorial) / this is done too
- new post with 1 example (win anim manager + direct2D) / currently working on (here is where nothing is working, of course)
- maybe another post with a second d2D sample - not sure

when all is done and I leave this project I will add a second zip in the first post with the c source code.

fasecero

OK, the library is working, still needs some tweaks here and there, and there is still a lot more to encapsulate in the future, these technologies are HUGE. You can download the library in my first post. Unzip and put animation.dll and animation.inc on a new folder and remember to make an import library for the dll.

This first example uses Media Fundation (DirectShow replacement) to play a video: avi, mp4, ... the video will fill the window's client area, maintaining the aspect ratio.


$INCLUDE "windowssdk.inc"
$INCLUDE "animation.inc"

$USE "animation.lib" ' remember to create this import library in tools menu!

pointer mfdata = 0
INT paused = FALSE
WINDOW w1
OPENWINDOW w1,0,0,600,400,@MINBOX|@MAXBOX|@SIZE|@NOAUTODRAW,NULL,"Simple Window",&w1_handler
OnInit()

' main loop
WAITUNTIL w1 = 0
END

' window procedure
SUB w1_handler(), INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW w1

CASE @IDCLOSEWINDOW
OnClose()
CLOSEWINDOW w1

CASE @IDPAINT
MFVideoRefresh(mfdata)

CASE @IDKEYUP
IF @WPARAM = 32 THEN
paused = NOT(paused)

IF paused THEN
MFVideoPause(mfdata)
ELSE
MFVideoPlay(mfdata)
ENDIF
ENDIF
ENDSELECT

RETURN 0
ENDSUB

SUB OnInit()
CoInitialize(0)
mfdata = MFVideoInit(w1.hwnd)
OpenVideo()
ENDSUB

SUB OpenVideo()
ISTRING filename[MAX_PATH]
STRING filter = "All Files (*.*)|*.*|MP4 Videos (*.mp4)|*.mp4||"

filename = FILEREQUEST("Select video file",0,1,filter,"mp4",0, "C:\\")

IF LEN(filename) THEN
MFVideoLoadFromFile(mfdata, S2W(filename))
MFVideoPlay(mfdata)
MFVideoLoop(mfdata, TRUE)

SETCAPTION w1, "Press spacebar to pause/resume video"
ENDIF
ENDSUB

SUB OnClose()
MFVideoShutdown(mfdata)
CoUninitialize()
ENDSUB


The second example uses Direct2D to play an animated gif file. If you removed everything related to the gif you will get the minimal Direct2D application. The gif will fill the window's client area or it will follow the mouse cursor, you can change this behavior by clicking on the window. It is important to note that for Direct2D to work properly, the use of @NOAUTODRAW is mandatory. In case you don't have a .gif file I was using this one for testing: https://www.deviantart.com/shadedancer619/art/Mini-waterfall-seamless-animated-gif-639758554


$INCLUDE "windowssdk.inc"
$INCLUDE "animation.inc"

$USE "animation.lib"

' direct2d variables
pointer render = 0
' gif variables
INT TIMER_1 = 1
pointer gifhandle = 0
INT stretch_gif = FALSE
INT gifWidth = 0
INT gifHeight = 0
' window
WINDOW w1
OPENWINDOW w1,0,0,800,600,@MINBOX|@MAXBOX|@SIZE|@NOAUTODRAW,NULL,"Gif player",&w1_handler
OnInit()

' main loop
WAITUNTIL w1 = 0
END

' window procedure
SUB w1_handler(), INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW w1

CASE @IDCLOSEWINDOW
OnClose()
CLOSEWINDOW w1

CASE @IDPAINT
OnPaint()

CASE @IDSIZE
OnSize()

CASE @IDTIMER
SELECT @WPARAM
CASE TIMER_1
gifOnTimer(w1.hwnd, gifhandle, TIMER_1)
ENDSELECT

CASE @IDLBUTTONDN
stretch_gif = NOT(stretch_gif)
InvalidateRect(w1.hwnd, NULL, NULL)
ENDSELECT

RETURN 0
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' EVENTS
' -------------------------------------------------------------------------------------------------------------------

SUB OnInit()
CoInitialize(0)

' init direct2d library
HRESULT hr = D2DInit()
IF hr > 0 THEN
_MessageBox(0, "The initialization has failed with code: " + STR$(hr), "Direct2D", 0)
_SendMessage(w1.hwnd, WM_CLOSE, 0, 0)
RETURN
ENDIF

' create the direct2d render target
WINRECT rc
GetClientRect(w1.hwnd, rc)
render = D2DRenderTargetCreate(w1.hwnd, rc.right, rc.bottom)

OpenGif()
ENDSUB

SUB OnClose()
gifDetach(w1.hwnd, gifhandle)

' destroy direct2d render target
D2DRenderTargetDestroy(render)

' finish direct2d library
D2DFinish()

CoUninitialize()
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' GIF ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB OpenGif()
ISTRING filename[MAX_PATH]
STRING filter = "All Files (*.gif)|*.gif|Gif files (*.gif)|*.gif||"

filename = FILEREQUEST("Select gif file",0,1,filter,"gif",0, GETSTARTPATH())

IF LEN(filename) THEN
gifhandle = gifAttach(w1.hwnd, TIMER_1, S2W(filename), render)
GifGetSize(gifhandle, gifWidth, gifHeight)

SETCAPTION w1, "Gif player - Click to fill the client area"
ENDIF
ENDSUB

SUB OnSize()
WINRECT rc
GetClientRect(w1.hwnd, rc)
D2DRenderTargetResize(render, rc.right, rc.bottom) ' update render target area
ENDSUB

SUB OnPaint()
IF render THEN
INT width = 0
INT height = 0

D2DRenderBegin(render)
D2DFillSolidColor(render, 255, 255, 255,  255)

IF stretch_gif THEN
' the gif will fill the client area
RenderGif(gifhandle)
ELSE
' the gif will follow the mouse
D2DRenderGetSize(render, width, height)

POINT p
GetCursorPos(&p)
ScreenToClient(w1.hwnd, &p)
RenderGif(gifhandle, p.x - gifWidth/2.0, p.y - gifHeight/2.0, p.x + gifWidth/2.0, p.y + gifHeight/2.0)
ENDIF

' if anything doesn't work this is a good spot to check what's going on
HRESULT hr = D2DRenderEnd(render)   ' in case of error 'hr' will have the error code
'_MessageBox(0, STR$(hr), "render error", 0)
ENDIF
ENDSUB

Brian

Fasecero,

Works great here. Tried the Direct2D on the downloaded GIF, and the Media Foundation sample on one of my favourite music videos, Big in Japan, by Alphaville

Will it show a progress bar on the bottom of the video in future? Just wondered

Brian

fasecero

Brian,

I had not thought about it but it's a good idea, I could also add seek during playback using the mouse and draw the elapsed time/remaining/total. After publishing the two examples that I still have left (currently playing with them) will check that out.

Alphaville, huh? That name sounds familiar, doesn't it? I friggin love the 80's/90's. Alphaville like you said, Sting, Simple Minds, Van Halen, UB40, Aerosmith, Prince, Queen, David Bowie, Phil Collins, U2, Pink Floyd, ... ugh so many others. This list would never end! Somethimes I search some playlist about these bands on Youtube, minimize the window and just listen to the music while I surf around the web or when programming and stuff :)

A few days ago I was watching this live performance by David Gilmour, how on earth can he play like that? It's beyong this world
https://www.youtube.com/watch?v=LTseTg48568


fasecero

Windows Animation Manager is a mechanism to make any kind of animations. You can drop Direct2D and use gdi, or gdiplus, or a game engine. These sets of functions can help to achieve complex animations regardless the render engine. The sample fades the window's background to a new randomly-generated color every time you click over the window client area. It's just an excuse to demostrate how it works.



$INCLUDE "animation.inc"

' animation variables
pointer storyboard = 0 ' always initialize a storyboard with a zero
pointer redvar, greenvar, bluevar ' animation variables
' direct2d variables
pointer render

INT red, green, blue

' window
WINDOW w1
OPENWINDOW w1,0,0,600,400,@MINBOX|@MAXBOX|@SIZE|@NOAUTODRAW,NULL,"Click to animate the background color",&w1_handler
SETFONT w1, "Segoe UI", 9, 400
OnInit()

' main loop
WAITUNTIL w1 = 0
END

' window procedure
SUB w1_handler(), INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW w1

CASE @IDCLOSEWINDOW
OnClose()
CLOSEWINDOW w1

CASE @IDLBUTTONDN
' change and animate the background color
AnimateBackroundColor(GetRandomNumber(0, 255), GetRandomNumber(0, 255), GetRandomNumber(0, 255))

CASE @IDPAINT
OnPaint()

CASE @IDSIZE
OnSize()
ENDSELECT

RETURN 0
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' EVENTS
' -------------------------------------------------------------------------------------------------------------------

SUB OnInit()
CoInitialize(0)
SeedRandomNumbers() ' randomize number generation

' init animation library
HRESULT hr = UIAnimationInit(&OnAnimationTick)
IF hr > 0 THEN
_MessageBox(0, "The initialization has failed with code: " + STR$(hr), " Windows Animation Manager", 0)
_SendMessage(w1.hwnd, WM_CLOSE, 0, 0)
RETURN
ENDIF

' init direct2d library
hr = D2DInit()
IF hr > 0 THEN
_MessageBox(0, "The initialization has failed with code: " + STR$(hr), " Direct2D", 0)
_SendMessage(w1.hwnd, WM_CLOSE, 0, 0)
RETURN
ENDIF

' create the direct2d render target
WINRECT rc
GetClientRect(w1.hwnd, rc)
render = D2DRenderTargetCreate(w1.hwnd, rc.right, rc.bottom)

' initial background color
red = 255
green = 255
blue = 255

' create animation variables
redvar = UIAnimationVariableAdd(red, 0, 255) ' initial, min, max
greenvar = UIAnimationVariableAdd(green, 0, 255)
bluevar = UIAnimationVariableAdd(blue, 0, 255)

OnPaint() ' draw the initial state
ENDSUB

SUB OnClose()
' delete animmation variables
UIAnimationVariableRemove(redvar)
UIAnimationVariableRemove(greenvar)
UIAnimationVariableRemove(bluevar)

' delete the storyboard if still exists
IF storyboard THEN UIAnimationStoryboardRemove(storyboard)

' finish the animation library
UIAnimationFinish()

' destroy direct2d render target
D2DRenderTargetDestroy(render)

' finish direct2d library
D2DFinish()

CoUninitialize()
ENDSUB

SUB OnPaint()
Paint()
SETCAPTION (w1, "Click to animate the background color" + "   -   R:" + STR$(red) + "    G:" + STR$(green) + "    B:" + STR$(blue) + "         ")
ENDSUB

SUB OnSize()
UpdateSize()
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' DIRECT2D ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB Paint()
D2DRenderBegin(render)
D2DFillSolidColor(render, red, green, blue, 255)
D2DRenderEnd(render)
ENDSUB

SUB UpdateSize()
WINRECT rc
GetClientRect(w1.hwnd, rc)
D2DRenderTargetResize(render, rc.right, rc.bottom) ' update render target area
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' ANIMATION ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB AnimateBackroundColor(INT red, INT green, INT blue) ' this function encapsulates the whole animation info
' create a new storyboard
storyboard = UIAnimationStoryBoardAdd(storyboard) ' this will delete the current storyboard and create a new one

IF storyboard THEN
' create one transition per variable
pointer transitionred = UIAnimationTransitionAccDecAdd(1.0, red, 0.5, 0.5) ' duration, final value, acc ratio, dec ratio
pointer transitiongreen = UIAnimationTransitionAccDecAdd(1.0, green, 0.5, 0.5)
pointer transitionblue = UIAnimationTransitionAccDecAdd(1.0, blue, 0.5, 0.5)

' comunicate the transition with its variable
UIAnimationStoryBoardTransitionJoin(storyboard, transitionred, redvar)
UIAnimationStoryBoardTransitionJoin(storyboard, transitiongreen, greenvar)
UIAnimationStoryBoardTransitionJoin(storyboard, transitionblue, bluevar)

' play the animation
UIAnimationStoryboardPlay(storyboard)

' delete the transitions (no need to wait till the animation finish)
UIAnimationTransitionRemove(transitionred)
UIAnimationTransitionRemove(transitiongreen)
UIAnimationTransitionRemove(transitionblue)
ENDIF
ENDSUB

SUB OnAnimationTick() ' this function is called every time any animation needs an update
IF UIAnimationStoryboardIsPlaying(storyboard) THEN
' get current values
red = UIAnimationVariableGetCurrentValue(redvar)
green = UIAnimationVariableGetCurrentValue(greenvar)
blue = UIAnimationVariableGetCurrentValue(bluevar)

' update the window, this will call @IDPAINT
InvalidateRect(w1.hwnd, NULL, NULL)
ENDIF
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' RANDOM NUMBER GENERATION
' -------------------------------------------------------------------------------------------------------------------

SUB SeedRandomNumbers() ' seeding for the first time only!
srand(GetTickCount())
ENDSUB

SUB GetRandomNumber(INT min, INT max), INT ' range : [min, max]
   RETURN min + _rand() % (max + 1 - min)
ENDSUB


Reference tutorial to create animations:

1) Call UIAnimationInit when the program start
2) Call UIAnimationFinish when the program ends

3) Create one global animation variable per value you need to fade from one value to another (such as position, size, color, or opacity)

4) Create a function to setup your animation:

SUB AnimationFunction()
   4.1) Create a storyboard using UIAnimationStoryBoardAdd. A storyboard is a collection of transitions applied to one or more animation variables over time.
   4.2) Create a transition for each animation variable. Setup the duration in seconds, final value and accelerator rates.
   4.3) Link each animation variable with its transition using UIAnimationStoryBoardTransitionJoin
   4.4) Play the animation using UIAnimationStoryboardPlay
   4.5) Clean up your transitions (no need to wait till the animation finish)
ENDSUB

5) Process the animation tick function, wich is called every time the animation needs an update

SUB OnAnimationTick()
   5.1) Get the current value of each animation variable using UIAnimationVariableGetCurrentValue
   5.2) Update your ui with the current values
ENDSUB

fasecero

This final example combines Windows Animation Manager + Direct2D. These two combined can achieve impressive results. This example still needs a few readjustments, for instance adjusting the layout after resizing the window. I'm excited about the idea of using these technologies in CUSTOMDRAW / OWNERDRAW controls, so I have something to play with.

Note: for this example to work you need to unzip the content of the resources.zip file below in the dll folder location. It has some images and one aditional .gif file inside.



$INCLUDE "animation.inc"

SETPRECISION 0

' animation variables
pointer storyboard1 = 0 ' movement to big
pointer storyboard2 = 0 ' movement to small
pointer movement[6, 5] ' animated variables
' direct2d variables
pointer render = 0
pointer brush ' brush handle
pointer images[6] ' bitmap handles
pointer textformat ' D2D font equivalent
' location variables
WINRECT area[6] ' image rectange fixed areas
WINRECT animArea[6] ' image rectange animated areas
INT position[6] ' image order inside rectangles
WINRECT textarea
' gif
INT TIMER_1 = 1
pointer gifhandle = 0
INT gifWidth = 0
INT gifHeight = 0
' other
INT posClick
pointer temp, temp2, temp3 ' helper pointer for arrays
INT j, k

' window
WINDOW w1
OPENWINDOW w1,0,0,1000,600,@MINBOX|@MAXBOX|@SIZE|@NOAUTODRAW|@HIDDEN,NULL,"Direct2D sample",&w1_handler
OnInit()
SHOWWINDOW w1, @SWMAXIMIZED
CreateAnimationVariables()

' main loop
WAITUNTIL w1 = 0
END

' window procedure
SUB w1_handler(), INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW w1

CASE @IDCLOSEWINDOW
OnClose()
CLOSEWINDOW w1

CASE @IDLBUTTONDN
OnMouseClick()

CASE @IDPAINT
OnPaint()

CASE @IDSIZE
OnSize()

CASE @IDTIMER
SELECT @WPARAM
CASE TIMER_1
gifOnTimer(w1.hwnd, gifhandle, TIMER_1)
ENDSELECT
ENDSELECT

RETURN 0
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' EVENTS
' -------------------------------------------------------------------------------------------------------------------

SUB OnInit()
CoInitialize(0)

' init animation library
HRESULT hr = UIAnimationInit(&OnAnimationTick)
IF hr > 0 THEN
_MessageBox(0, "The initialization has failed with code: " + STR$(hr), " Windows Animation Manager", 0)
_SendMessage(w1.hwnd, WM_CLOSE, 0, 0)
RETURN
ENDIF

' init direct2d library
hr = D2DInit()
IF hr > 0 THEN
_MessageBox(0, "The initialization has failed with code: " + STR$(hr), "Direct2D", 0)
_SendMessage(w1.hwnd, WM_CLOSE, 0, 0)
RETURN
ENDIF

' create the direct2d render target
WINRECT rc
GetClientRect(w1.hwnd, rc)
render = D2DRenderTargetCreate(w1.hwnd, rc.right, rc.bottom)

' create a solid brush
brush = D2DSolidBrushCreate(render, 0, 0, 0, 255)

' initial areas
CalculateAreas(rc)

' initial values
FOR j = 1 TO 5
position[j] = j
NEXT j

' load bitmap handles
FOR j = 1 TO 5
iwstring fullpath[MAX_PATH] = GETSTARTPATHW + L"image" + WSTR$(j) + L".jpeg"
temp = D2DBitmapLoadFromFile(render, &fullpath, 0, 0)
images[j] = temp + 0
NEXT j

' create a font
textformat = D2DTextFormatCreate(render, L"SegoeUI", 20.0, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_PARAGRAPH_ALIGNMENT_CENTER)

' load gif
gifhandle = gifAttach(w1.hwnd, TIMER_1, S2W(getstartpath + "folder.gif"), render)
GifGetSize(gifhandle, gifWidth, gifHeight)
ENDSUB

SUB OnClose()
' delete gif
gifDetach(w1.hwnd, gifhandle)

' delete brush
D2DSolidBrushRelease(brush)

' delete the font
D2DTextFormatRelease(textformat)

' delete bitmap handles
FOR j = 1 TO 5
temp = images[j] + 0
D2DBitmapRelease(temp)
NEXT j

' destroy direct2d render target
D2DRenderTargetDestroy(render)

' finish direct2d library
D2DFinish()

' delete animmation variables
FOR j = 1 TO 5
FOR k = 1 TO 4
temp = movement[j, k] + 0
UIAnimationVariableRemove(temp)
NEXT k
NEXT j

' delete the storyboard if still exists
IF storyboard1 THEN UIAnimationStoryboardRemove(storyboard1)
IF storyboard2 THEN UIAnimationStoryboardRemove(storyboard2)

' finish the animation library
UIAnimationFinish()

CoUninitialize()
ENDSUB

SUB OnPaint()
Paint()
ENDSUB

SUB OnSize()
UpdateSize()
ENDSUB

SUB OnMouseClick()
IF UIAnimationStoryboardIsPlaying(storyboard1) = 1 OR UIAnimationStoryboardIsPlaying(storyboard2) = 1 THEN RETURN

posClick = 0

POINT p
GetCursorPos(&p)
ScreenToClient(w1.hwnd, &p)

FOR j = 1 TO 4
IF area[j].left <= p.x AND area[j].right >= p.x THEN
IF area[j].top <= p.y AND area[j].bottom >= p.y THEN
posClick = j
ENDIF
ENDIF
NEXT j

IF posClick THEN
' switch places
INT p1 = position[5]
INT p2 = position[posClick]
position[5] = p2
position[posClick] = p1

' make the animation
AnimateImages()
ENDIF
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' LOCATION ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB CalculateAreas(WINRECT rc)
' based on client area size calculate image's position
INT d = 16
INT tx = 120
INT ty = 60

INT sy = (rc.bottom - 6 * d - ty) / 4.0
INT by = rc.bottom - 2 * d

INT sx = 1.2 * sy

INT dx = sx
IF tx > sx THEN dx = tx

INT bx = rc.right - 3 * d - dx

' small rectangles
FOR j = 1 TO 4
area[j].left  = d
area[j].right = area[j].left + sx
area[j].top = d * (j) + sy * (j - 1)
area[j].bottom = area[j].top + sy - 20
NEXT j

' big rectangle
area[5].left  = d + dx + d
area[5].right = rc.right - d
area[5].top = d
area[5].bottom = rc.bottom - d

FOR j = 1 TO 5
INT current = position[j]
animArea[current] = area[j]
NEXT j

' text rectangle
textarea.left = d
textarea.top  = 5 * d + 4 * sy
textarea.right = textarea.left + tx
textarea.bottom = textarea.top + ty
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' DIRECT2D ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB Paint()
IF render THEN
D2DRenderBegin(render)
D2DFillSolidColor(render, 255, 255, 255,  255)

' render gif
RenderGif(gifhandle, textarea.left + 80, textarea.top - 30, textarea.left + 80 + gifWidth, textarea.top - 30 + gifHeight)

FOR j = 1 to 5
'INT d = -1
'D2DRenderOutline(render, brush, area[j].left - d, area[j].top - d, area[j].right + d, area[j].bottom + d)
NEXT j

FOR j = 1 to 5
temp = images[j] + 0
D2DRenderBitmap(render, temp, animArea[j].left, animArea[j].top, animArea[j].right, animArea[j].bottom, 255)
NEXT j

' render text
D2DRenderText(render, brush, L"Select an image", textarea.left, textarea.top, textarea.right, textarea.bottom, textformat)

' if anything doesn't work this is a good spot to check what's going on
HRESULT hr = D2DRenderEnd(render)   ' in case of error 'hr' will have the error code
'_MessageBox(0, STR$(hr), "render error", 0)
ENDIF
ENDSUB

SUB UpdateSize()
IF UIAnimationStoryboardIsPlaying(storyboard1) = 1 OR UIAnimationStoryboardIsPlaying(storyboard2) = 1 THEN RETURN

WINRECT rc
GetClientRect(w1.hwnd, rc)
D2DRenderTargetResize(render, rc.right, rc.bottom) ' update render target area
CalculateAreas(rc)
InvalidateRect(w1.hwnd, NULL, NULL)
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' ANIMATION ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB CreateAnimationVariables()
FOR j = 1 TO 5
FOR k = 1 TO 4
SELECT k
CASE 1 : temp = UIAnimationVariableAdd(area[j].left, 0, 32000)
CASE 2 : temp = UIAnimationVariableAdd(area[j].top, 0, 32000)
CASE 3 : temp = UIAnimationVariableAdd(area[j].right, 0, 32000)
CASE 4 : temp = UIAnimationVariableAdd(area[j].bottom, 0, 32000)
ENDSELECT

movement[j, k] = temp + 0
NEXT k
NEXT j
ENDSUB

SUB AnimateImages() ' this function encapsulates the whole animation info
' create storyboards
storyboard1 = UIAnimationStoryBoardAdd(storyboard1) ' movement to big
storyboard2 = UIAnimationStoryBoardAdd(storyboard2) ' movement to small

IF storyboard1 <> 0 AND storyboard2 <> 0 THEN
' create one transition per variable
pointer transition[3, 5]
float duration = 0.7
INT current_area = 0
INT current_var = 0

FOR j = 1 TO 2
IF j = 1 THEN current_area = 5 ELSE current_area = posClick
IF j = 1 THEN current_var = position[5] ELSE current_var = position[posClick]

FOR k = 1 TO 4
SELECT k
CASE 1 : temp = UIAnimationTransitionAccDecAdd(duration, area[current_area].left, 0.5, 0.5) ' duration, final value, acc ratio, dec ratio
CASE 2 : temp = UIAnimationTransitionAccDecAdd(duration, area[current_area].top, 0.5, 0.5)
CASE 3 : temp = UIAnimationTransitionAccDecAdd(duration, area[current_area].right, 0.5, 0.5)
CASE 4 : temp = UIAnimationTransitionAccDecAdd(duration, area[current_area].bottom, 0.5, 0.5)
ENDSELECT

transition[j, k] = temp + 0

' comunicate the transition with its variable
temp2 = movement[current_var, k] + 0

IF j = 1 THEN
UIAnimationStoryBoardTransitionJoin(storyboard1, temp, temp2)
ELSE
UIAnimationStoryBoardTransitionJoin(storyboard2, temp, temp2)
ENDIF
NEXT k
NEXT j

' play the animation
UIAnimationStoryboardPlay(storyboard1)
UIAnimationStoryboardPlay(storyboard2)

' delete the transitions (no need to wait till the animation finish)
FOR j = 1 TO 2
FOR k = 1 TO 4
temp3 = transition[j, k] + 0
UIAnimationTransitionRemove(temp3)
NEXT k
NEXT j
ENDIF
ENDSUB

SUB OnAnimationTick() ' this function is called every time any animation needs an update
IF UIAnimationStoryboardIsPlaying(storyboard1) = 1 OR UIAnimationStoryboardIsPlaying(storyboard2) = 1 THEN

FOR j = 1 TO 2
IF j = 1 THEN current_area = 5 ELSE current_area = posClick
IF j = 1 THEN current_var = position[5] ELSE current_var = position[posClick]

FOR k = 1 TO 4
temp2 = movement[current_var, k] + 0
INT value = UIAnimationVariableGetCurrentValue(temp2)

SELECT k
CASE 1 : animArea[current_var].left = value
CASE 2 : animArea[current_var].top = value
CASE 3 : animArea[current_var].right = value
CASE 4 : animArea[current_var].bottom = value
ENDSELECT
NEXT k
NEXT j

' update the window, this will call @IDPAINT
InvalidateRect(w1.hwnd, NULL, NULL)
ENDIF
ENDSUB


I have also expanded the Media Fundation example based on Brian's sugestions as well. Now it shows the elapsed time and the remaining time. It shows a progress bar while the video is displayed and you can seek the video position clicking over the progress bar. For this example to work, you have to download the library again (on my first post) and import the dll again as well since I had to add two new functions.


$INCLUDE "windowssdk.inc"
$INCLUDE "shlwapi.inc"
$INCLUDE "animation.inc"

$USE "animation.lib"

' direct2d variables
pointer render1 = 0
pointer render2 = 0
pointer blackbrush1, blackbrush2
pointer greenbrush1
pointer whitebrush1, whitebrush2
pointer textformat1, textformat2
' media fundation
CONST TIMER_1 = 1
pointer mfdata = 0
INT paused = FALSE
UINT64 duration ' video file duration in 100-nanosecond units
' other
INT overlayHeight = 40
INT progHeight = 3
INT show_overlay = 1
string name
' window
WINDOW w1, w2
OPENWINDOW w1,0,0,600,400,@MINBOX|@MAXBOX|@SIZE|@NOAUTODRAW|@HIDDEN,NULL,"Media Fundation Demo",&w1_handler
OPENWINDOW w2,0,0,600,300,@NOCAPTION|@NOAUTODRAW,w1, "", &w2_handler
OnInit()
SHOWWINDOW w1, @SWSHOW

' main loop
WAITUNTIL w1 = 0
END

' -------------------------------------------------------------------------------------------------------------------
' WINDOW PROCEDURES
' -------------------------------------------------------------------------------------------------------------------

SUB w2_handler(), INT
SELECT @MESSAGE
CASE @IDPAINT
OnPaint2()

CASE @IDSIZE
OnSize2()

CASE @IDCLOSEWINDOW
CLOSEWINDOW w2

CASE @IDKEYUP
OnKeyboard()
ENDSELECT

RETURN 0
ENDSUB

SUB w1_handler(), INT
SELECT @MESSAGE
CASE @IDCREATE
CENTERWINDOW w1

CASE @IDCLOSEWINDOW
OnClose()
CLOSEWINDOW w1

CASE @IDPAINT
OnPaint1()

CASE @IDSIZE
OnSize1()

CASE @IDLBUTTONDN
OnMouseClick()

CASE @IDKEYUP
OnKeyboard()

CASE @IDTIMER
SELECT @WPARAM
CASE TIMER_1
InvalidateRect(w1.hwnd, NULL, NULL)
ENDSELECT
ENDSELECT

RETURN 0
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' EVENTS
' -------------------------------------------------------------------------------------------------------------------

SUB OnInit()
CoInitialize(0)

' init direct2d library
HRESULT hr = D2DInit()
IF hr > 0 THEN
_MessageBox(0, "The initialization has failed with code: " + STR$(hr), "Direct2D", 0)
_SendMessage(w1.hwnd, WM_CLOSE, 0, 0)
RETURN
ENDIF

' create the direct2d render target
WINRECT rc
GetClientRect(w1.hwnd, rc)
render1 = D2DRenderTargetCreate(w1.hwnd, rc.right, rc.bottom)
GetClientRect(w2.hwnd, rc)
render2 = D2DRenderTargetCreate(w2.hwnd, rc.right, rc.bottom)

' create a solid brush
blackbrush1 = D2DSolidBrushCreate(render1, 0, 0, 0, 255)
blackbrush2 = D2DSolidBrushCreate(render2, 0, 0, 0, 255)
greenbrush1 = D2DSolidBrushCreate(render1, 0, 150, 0, 255)
whitebrush1 = D2DSolidBrushCreate(render1, 255, 255, 255, 255)
whitebrush2 = D2DSolidBrushCreate(render2, 255, 255, 255, 255)

' create fonts
textformat1 = D2DTextFormatCreate(render1, L"SegoeUI", 15.0, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_PARAGRAPH_ALIGNMENT_CENTER)
textformat2 = D2DTextFormatCreate(render2, L"SegoeUI", 20.0, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_TEXT_ALIGNMENT_CENTER, DWRITE_PARAGRAPH_ALIGNMENT_CENTER)

ENDSUB

SUB OnClose()
' delete the fonts
D2DTextFormatRelease(textformat1)
D2DTextFormatRelease(textformat2)

' delete brush
D2DSolidBrushRelease(blackbrush1)
D2DSolidBrushRelease(blackbrush2)
D2DSolidBrushRelease(greenbrush1)
D2DSolidBrushRelease(whitebrush1)
D2DSolidBrushRelease(whitebrush2)

' destroy direct2d render target
D2DRenderTargetDestroy(render1)
D2DRenderTargetDestroy(render2)

' finish direct2d library
D2DFinish()

MFVideoShutdown(mfdata)
CoUninitialize()
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' KEYBOARD & MOUSE EVENTS
' -------------------------------------------------------------------------------------------------------------------

SUB OnMouseClick()
IF mfdata THEN
WINRECT rc
GetClientRect(w1.hwnd, rc)

POINT p
GetCursorPos(&p)
ScreenToClient(w1.hwnd, &p)

UINT64 width = rc.right
UINT64 mx = p.x
UINT64 pos = (mx/FLT(width)) * duration

MFVideoSetPosition(mfdata, pos)
ENDIF
ENDSUB

SUB OnKeyboard()
SELECT @WPARAM
CASE 32 ' spacebar
paused = NOT(paused)

IF paused THEN
MFVideoPause(mfdata)
ELSE
MFVideoPlay(mfdata)
ENDIF
CASE 79 ' O letter
OpenVideo()
CASE 27 ' escape key
'
CASE 17 ' control key
INT value = show_overlay
IF value THEN show_overlay = 0 ELSE show_overlay = 1
OnSize1()
ENDSELECT
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' MEDIA FUNDATION ROUTINES
' -------------------------------------------------------------------------------------------------------------------

DECLARE CDECL EXTERN sprintf(POINTER p, POINTER p, ...), INT

SUB FormatTime(UINT64 _time, INT offset), string
string time = ""
UINT64 t = _time / 10000000 ' duration in seconds
INT h = floor(t/3600)
int m = (t/60)%60
int s = t%60 + offset
sprintf(time, "%02d:%02d:%02d", h, m, s)
RETURN time
ENDSUB

SUB OpenVideo()
ISTRING filename[MAX_PATH]
STRING filter = "All Files (*.*)|*.*|MP4 Videos (*.mp4)|*.mp4||"

filename = FILEREQUEST("Select video file",0,1,filter,"mp4",0, "C:\\")

IF LEN(filename) THEN
MFVideoShutdown(mfdata)
mfdata = MFVideoInit(w2.hwnd)
STOPTIMER w1, 1000
duration = MFVideoLoadFromFile(mfdata, S2W(filename))

MFVideoPlay(mfdata)
MFVideoLoop(mfdata, TRUE)
OnSize1()
OnSize2()
InvalidateRect(w1.hwnd, NULL, true)
InvalidateRect(w2.hwnd, NULL, true)

pointer p = PathFindFileName(&filename)
name = *<string>p

STARTTIMER w1, 33, TIMER_1
ENDIF
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' SIZE ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB OnSize1()
WINRECT rc
GetClientRect(w1.hwnd, rc)
D2DRenderTargetResize(render1, rc.right, rc.bottom) ' update render target area

INT height = 0
if show_overlay THEN height = overlayHeight

SETSIZE w2, 0, 0, rc.right, rc.bottom - height
ENDSUB

SUB OnSize2()
WINRECT rc
GetClientRect(w2.hwnd, rc)
D2DRenderTargetResize(render2, rc.right, rc.bottom) ' update render target area
ENDSUB

' -------------------------------------------------------------------------------------------------------------------
' PAINT ROUTINES
' -------------------------------------------------------------------------------------------------------------------

SUB OnPaint1()
INT width, height

IF render1 THEN
D2DRenderBegin(render1)
D2DRenderGetSize(render1, width, height)

IF mfdata = 0 THEN
D2DFillSolidColor(render1, 0, 0, 0, 255)
ELSE
' draw the overlay background
D2DRenderRectangle(render1, blackbrush1, 0, height - overlayHeight, width, height)

' calculate progress_width
UINT64 pos = MFVideoGetPosition(mfdata)
UINT64 progress_width = (pos/FLT(duration)) * width
D2DRenderRectangle(render1, greenbrush1, 0, height - progHeight- 1, progress_width, height - 1)

INT rem_width = 60

' draw elapsed time
wstring timeElapsed = S2W(FormatTime(pos, 1))
D2DRenderText(render1, whitebrush1, timeElapsed + L"      " + s2w(name), 10, height - overlayHeight, width - 10 - rem_width - 20, height, textformat1)
' draw remaining time
wstring timeRemaining = S2W("-" + FormatTime(duration - pos, 1))
D2DRenderText(render1, whitebrush1, timeRemaining, width - 10 - rem_width, height - overlayHeight, width - 10, height, textformat1)
ENDIF

' if anything doesn't work this is a good spot to check what's going on
HRESULT hr = D2DRenderEnd(render1)   ' in case of error 'hr' will have the error code
'_MessageBox(0, STR$(hr), "render error", 0)
ENDIF
ENDSUB

SUB OnPaint2()
INT width, height

IF mfdata THEN
MFVideoRefresh(mfdata)
ELSE
IF render2 THEN
D2DRenderBegin(render2)
D2DRenderGetSize(render2, width, height)
D2DFillSolidColor(render2, 0, 0, 0, 255)

' render text
wstring text = L"Press 'O' to open a video file" + WCHR$(10) + L"Press 'SPACEBAR' to pause/resume video" _
+ WCHR$(10) + L"Press 'CONTROL' to show/hide progress"
D2DRenderText(render2, whitebrush2, text, 0, 0, width, height, textformat2)

' if anything doesn't work this is a good spot to check what's going on
HRESULT hr = D2DRenderEnd(render2)   ' in case of error 'hr' will have the error code
'_MessageBox(0, STR$(hr), "render error", 0)
ENDIF
ENDIF
ENDSUB


This is pretty much it! Have fun!

Brian

Fasecero,

Just been trying the jpeg viewer - very impressive! I was trying it with some of my own pics, and it didn't see them. I changed their extension from jpg to jpeg, and increased the number in the j loop, but no banana. Is it designed to only show four items. Can it show more than four items?

But great work - I loved it

Brian

fasecero

Brian, thanks. Yes, the sample was composed just for the 4 animations, not for the images. For the left side to handle an indeterminate number of images it would require to be completely rewritten. I'll take a look but after some time. Thanks for trying in out! These new techs opens a new door to animated ownerdraw controls, custom animated windows frames, etc.

h3kt0r

Great samples, thank you for sharing this !