Page 1 of 3

FlipBook Module

Posted: Thu Jan 14, 2021 8:55 pm
by Mr.L
Just a FlipBook on a Canvas Gadget.
Use your mouse+left Button to drag the page corners around.
Have fun!

(the code is free - no restrictions, do whatever you want with it)

Code: Select all

;  -=FlipBook=-
; --------------
; 01/2021 - Mr.L

DeclareModule FlipBook
	Structure FlipBook
		Array page.i(0)
		canvasID.i
		x.d
		y.d
		width.d
		height.d
		nrPages.i
		currentPage.i
		cornerX.l
		cornerY.l
		backgroundColor.i
	EndStructure
	
	Declare New(canvasID, x, y, width, height, backgroundColor)
	Declare AddPage(*book.FlipBook, makeImage, color, borderColor, borderWidth.d)
	Declare DrawBook(*book.FlipBook, x, y, doAnimation = #True)
	Declare HandleEvent(*book.FlipBook, event)
EndDeclareModule

Module FlipBook
	EnableExplicit
	
	#epsilon = -0.0000001
	#flipTimer = 1000
	
	Procedure.d Clamp(value.d, min.d, max.d)
		If value < min
			ProcedureReturn min
		ElseIf value > max
			ProcedureReturn max
		EndIf
		ProcedureReturn value
	EndProcedure
	
	Procedure New(canvasID, x, y, width, height,backgroundColor)
		Protected *book.FlipBook = AllocateStructure(FlipBook)
		If *book
			*book\canvasID = canvasID
			*book\x = x
			*book\y = y
			*book\width = width
			*book\height = height
			*book\backgroundColor = backgroundColor
		EndIf
		ProcedureReturn *book
	EndProcedure
	
	Procedure AddPage(*book.FlipBook, makeImage, color, borderColor, borderWidth.d)
		If *book
			Protected pageNr = *book\nrPages
			*book\nrPages + 1
			ReDim *book\page(*book\nrPages)
			If makeImage
				*book\page(pageNr) = CreateImage(#PB_Any, *book\width, *book\height, 32, color)
				If IsImage(*book\page(pageNr)) And StartVectorDrawing(ImageVectorOutput(*book\page(pageNr)))
					AddPathBox(0, 0, *book\width, *book\height)
					VectorSourceColor(borderColor)
					StrokePath(borderWidth, #PB_Path_Preserve)
					VectorSourceColor(RGBA(128,128,128,200))
					StrokePath(4)
					StopVectorDrawing()
				EndIf
			EndIf
			ProcedureReturn pageNr
		EndIf
		ProcedureReturn 0
	EndProcedure
	
	Procedure Mirror(x1,y1.d,x2.d,y2.d,x3.d,y3.d, *resultX.Double, *resultY.Double)
		Protected m.d = (y3 - y2) / (x3 - x2 + #epsilon)
		Protected c.d = (x3 * y2 - x2 * y3) / (x3 - x2 + #epsilon)
		Protected d.d = (x1 + (y1 - c) * m) / (1 + m * m + #epsilon)
		*resultX\d = 2 * d - x1
		*resultY\d = 2 * d * m - y1 + 2 * c
	EndProcedure
	
	Procedure DrawHighlight(x1, y1, x2, y2, x3, y3, x4, y4, width, flags = #PB_Path_Default)
		Protected nx.d = y1 - y2
		Protected ny.d = x2 - x1
		Protected di.d = Sqr(nx * nx + ny * ny) + #epsilon
		Protected di1.d = Sqr(Pow(x1 - x3,2) + Pow(y1 - y3, 2))
		Protected di2.d = Sqr(Pow(x2 - x4,2) + Pow(y2 - y4, 2))
		If di1 > di2
			VectorSourceLinearGradient(x1, y1, x1 - (nx / di) * di1 * width, y1 - (ny / di) * di1 * width)
		Else
			VectorSourceLinearGradient(x2, y2, x2 - (nx / di) * di2 * width, y2 - (ny / di) * di2 * width)
		EndIf
		VectorSourceGradientColor(RGBA(225,225,225,0), 1.0)
		VectorSourceGradientColor(RGBA(255,255,255,0), 0.8)
		VectorSourceGradientColor(RGBA(200,200,200,64), 0.3)
		VectorSourceGradientColor(RGBA(255,255,255,128), 0.1)
		VectorSourceGradientColor(RGBA(0,0,0,64), 0.0)
		FillPath(flags)
	EndProcedure
	
	Procedure DrawShadow(x1.d, y1.d, x2.d, y2.d, x3.d, y3.d, x4.d, y4.d, width.d, pos.d, alpha)
		Protected nx.d = y1 - y2
		Protected ny.d = x2 - x1
		Protected di.d = Sqr(nx * nx + ny * ny) + #epsilon
		
		Protected dx.d = (x1 + x2) * 0.5 - (x3 + x4) * 0.5
		Protected dy.d = (y1 + y2) * 0.5 - (y3 + y4) * 0.5
		Protected di2.d = Sqr(dx*dx+dy*dy)
		
		VectorSourceLinearGradient(x1, y1, x1 + (nx / di) * width, y1 + (ny / di) * width)
		VectorSourceGradientColor(RGBA(0,0,0,alpha * (1 - Abs(di2 / width))), 1)
		
		VectorSourceGradientColor(RGBA(255,255,255,0), pos)
		VectorSourceGradientColor(RGBA(255,255,255,0), 0)
		FillPath()
	EndProcedure
	
	Procedure DrawPage(*book.FlipBook, pageNr, x, y)
		If pageNr >= 0 And pageNr <= ArraySize(*book\page()) And IsImage(*book\page(pageNr))
			MovePathCursor(x, y)
			DrawVectorImage(ImageID(*book\page(pageNr)))
		EndIf
	EndProcedure
	
	Procedure DrawBook(*book.FlipBook, x, y, doAnimation = #True)
		Protected nextPageNr
		Protected.d nx, ny, di
		Protected.d px1, py1, px2, py2
		Protected.d midX, midY, midX1, midY1, midX2, midY2
		
		If StartVectorDrawing(CanvasVectorOutput(*book\canvasID)) = 0
			ProcedureReturn
		EndIf
		
		VectorSourceColor(*book\backgroundColor)
		FillVectorOutput()
		
		TranslateCoordinates(*book\x, *book\y)

		If *book\cornerX > 0
			nextPageNr = *book\currentPage
			x = Clamp(x, -*book\width * 1.5 , *book\width * 0.5 - 1)
		Else
			nextPageNr = *book\currentPage - 1
			x = Clamp(x, -*book\width * 0.5 , *book\width * 1.5 + 1)
		EndIf
		
		; draw left page if this is not the first page
		If *book\currentPage > 0 And *book\currentPage <= *book\nrPages + 1
			If *book\currentPage > *book\nrPages
				DrawPage(*book, *book\nrPages, -*book\width * 1.5, -*book\height * 0.5)
			Else
				DrawPage(*book, *book\currentPage - 1, -*book\width * 1.5, -*book\height * 0.5)
			EndIf
			AddPathBox(-*book\width * 1.5, -*book\height * 0.5, *book\width, *book\height)
			DrawHighlight(-*book\width * 1.5, -*book\height * 0.5, -*book\width * 1.5, *book\height * 0.5, 
			              -*book\width * 0.5, -*book\height * 0.5, -*book\width * 0.5, *book\height * 0.5, -1)
		EndIf
		
		; draw right page if this is not the last page
		If (*book\currentPage + 1) >= 0 And (*book\currentPage + 1) <= *book\nrPages
			DrawPage(*book, *book\currentPage, -*book\width * 0.5, -*book\height * 0.5)
			AddPathBox(-*book\width * 0.5, -*book\height * 0.5, *book\width, *book\height)
			DrawHighlight(-*book\width * 0.5, -*book\height * 0.5, -*book\width * 0.5, *book\height * 0.5, 
			              *book\width * 0.5, -*book\height * 0.5, *book\width * 0.5, *book\height * 0.5,  1)
		EndIf
		
		If doAnimation
			; calculate mirror axis
			midX = (x + *book\cornerX) * 0.5
			midY = (y + *book\cornerY) * 0.5
			
			nx = Pow(*book\cornerY - midY, 2) / (*book\cornerX - midX + #epsilon)
			ny = Pow(*book\cornerX - midX, 2) / (*book\cornerY - midY + #epsilon)
			
			midX1 = Clamp(midX - nx, -Abs(*book\cornerX), Abs(*book\cornerX))
			midY1 = *book\cornerY
			
			If ((*book\cornerY < 0) And ((midY - ny) < -*book\cornerY) And (y > *book\cornerY)) Or 
			   ((*book\cornerY > 0) And ((midY - ny) > -*book\cornerY) And (y < *book\cornerY))
				; mirror axis crosses vertical edge
				midX2 = *book\cornerX
				midY2 = midY - ny
			Else
				; mirror axis crosses horizontal edge
				midX2 = Clamp(MidX1 + (*book\height * (midX - midX1) / (midY1 - midY - #epsilon)) * Sign(*book\cornerY), -Abs(*book\cornerX), Abs(*book\cornerX))
				midY2 = -*book\cornerY
			EndIf
			
			; mirror the page corners
			Mirror(*book\cornerX, *book\cornerY, midX1,midY1,midX2,midY2,@px1,@py1)
			Mirror(*book\cornerX, -*book\cornerY,midX1,midY1,midX2,midY2,@px2,@py2)
			
			If (*book\cornerX > 0 And px2 > *book\cornerX) Or (*book\cornerX < 0 And px2 < *book\cornerX)
				px2 = midX2
				py2 = midY2
			EndIf
			
			If *book\cornerX < 0
				TranslateCoordinates(-*book\width, 0)
			EndIf
			
			; rotate and draw image
			If nextPageNr + Sign(*book\cornerX) >= 0 And nextPageNr + Sign(*book\cornerX) <= *book\nrPages
				MovePathCursor(midX1, midY1)
				AddPathLine(midX2, midY2)
				AddPathLine(px2 - (1 - Abs((*book\width * 0.5 * Sign(*book\cornerX) + px2) / *book\width)) * 50 * Sign(*book\cornerX), py2)
				AddPathLine(px1 - (1 - Abs((*book\width * 0.5 * Sign(*book\cornerX) + px1) / *book\width)) * 50 * Sign(*book\cornerX), py1)
				DrawShadow(midX1, midY1, midX2, midY2,
				           -*book\cornerX, *book\cornerY, *book\cornerX, -*book\cornerY,
				           *book\width * Sign(*book\cornerY * *book\cornerX), 0, 128)
				
				MovePathCursor(midX1, midY1)
				AddPathLine(midX2, midY2)
				AddPathLine(px2,py2)
				AddPathLine(px1,py1)
				ClosePath()
				
				SaveVectorState()
				ClipPath(#PB_Path_Preserve)
				
				SaveVectorState()
				If *book\cornerY < 0
					If *book\cornerX > 0
						RotateCoordinates(px1, py1, Degree(-ATan2(py2 - py1, px2 - px1)))
						DrawPage(*book, nextPageNr + Sign(*book\cornerX), px1, py1)
					Else
						RotateCoordinates(px1, py1, Degree(-ATan2(py1 - py2, px1 - px2)) + 180)
						DrawPage(*book, nextPageNr + Sign(*book\cornerX), px1 - *book\width, py1)
					EndIf
				Else
					RotateCoordinates(px1, py1, Degree(-ATan2(py1 - py2, px1 - px2)))
					If *book\cornerX > 0
						DrawPage(*book, nextPageNr + Sign(*book\cornerX), px1, py1 - *book\height)
					Else
						DrawPage(*book, nextPageNr + Sign(*book\cornerX), px1 - *book\width , py1 - *book\height)
					EndIf
				EndIf
				RestoreVectorState()
				
				DrawHighlight(midX1, midY1, midX2, midY2, px1, py1, px2, py2, Sign(*book\cornerY * *book\cornerX))
				RestoreVectorState()
			EndIf
			
			MovePathCursor(midX1, midY1)
			AddPathLine(midX2,midY2)
			AddPathLine(*book\cornerX, -*book\cornerY)
			AddPathLine(*book\cornerX,  *book\cornerY)
			ClosePath()
			If nextPageNr + Sign(*book\cornerX) * 2 < 0 Or nextPageNr + Sign(*book\cornerX) * 2 >= *book\nrPages
				VectorSourceColor(*book\backgroundColor)
				FillPath()
			Else
				ClipPath()
				DrawPage(*book, nextPageNr + Sign(*book\cornerX) * 2, -*book\width * 0.5, -*book\height * 0.5)
				
				AddPathBox(-*book\width * 0.5, -*book\height * 0.5, *book\width, *book\height)
				
				DrawHighlight(-*book\width * 0.5, -*book\height * 0.5, -*book\width * 0.5, *book\height * 0.5, 
				              *book\width * 0.5, -*book\height * 0.5, *book\width * 0.5, *book\height * 0.5, 
				              Sign(*book\cornerX), #PB_Path_Preserve)
				
				DrawShadow(midX1, midY1, midX2, midY2,
				           *book\cornerX, *book\cornerY, *book\cornerX, -*book\cornerY,
				           *book\width * Sign(-*book\cornerY * *book\cornerX), 0.95, 200)
				
			EndIf
		EndIf
		
		StopVectorDrawing()
	EndProcedure
	
	Procedure HandleEvent(*book.FlipBook, event)
		Protected mx, my
		Static lButton, autoFlip, grabX.d, grabY.d, targetX.d, targetY.d, nextPage
		
		Select event
			Case #PB_Event_Timer
				If EventTimer() = #flipTimer
					If autoFlip
						If (Abs(targetX - grabX)) > 1
							grabX + (targetX - grabX) * 0.3
							grabY + (targetY - grabY) * 0.3
							DrawBook(*book, grabX, grabY)
						Else
							*book\currentPage = nextPage
							autoFlip = 0
							RemoveWindowTimer(0, #flipTimer)
							DrawBook(*book, targetX, targetY, #False)
						EndIf
					EndIf
				EndIf
			Case #PB_Event_Gadget
				mx = GetGadgetAttribute(*book\canvasID, #PB_Canvas_MouseX)
				my = GetGadgetAttribute(*book\canvasID, #PB_Canvas_MouseY)
				If EventType() = #PB_EventType_LeftButtonDown
					If Abs(mx - (*book\x - *book\width * 0.5)) > *book\width * 0.65
						*book\cornerX = *book\width * Sign(mx - *book\x) * 0.5
						*book\cornerY = *book\height * Sign(my - *book\y) * 0.5
						If (*book\currentPage + Sign(*book\cornerX)) >= 0 And (*book\currentPage + Sign(*book\cornerX)) <= *book\nrPages
							lButton = 1
						EndIf
					EndIf
				ElseIf EventType() = #PB_EventType_LeftButtonUp
					If lButton
						lButton = 0
						If ((*book\cornerX > 0) And ((mx - *book\x) < *book\width * 0.25)) Or
						   ((*book\cornerX < 0) And ((mx - *book\x) > -*book\width * 1.25))
							nextPage = *book\currentPage + 2 * Sign(*book\cornerX)
							targetX = *book\width * 1.5 * Sign(-*book\cornerX) + 1
							targetY = *book\cornerY
						Else
							targetX = *book\cornerX * 1.01
							targetY = *book\cornerY * 0.99
						EndIf
						AddWindowTimer(0, #flipTimer, 25)
						autoFlip = 1
					EndIf
				ElseIf EventType() = #PB_EventType_MouseMove
					If autoFlip = 0 And lButton
						If *book\cornerX < 0
							mx + *book\width
						EndIf
						grabX = mx - *book\x
						grabY = my - *book\y
						DrawBook(*book, grabX, grabY)
						Delay(25)
					EndIf
				EndIf
		EndSelect

	EndProcedure
	
	DisableExplicit
EndModule

CompilerIf #PB_Compiler_IsMainFile
	OpenWindow(0,0,0,800,600,"FlipBook", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
	CanvasGadget(0,0,0,WindowWidth(0), WindowHeight(0))
	
	UseModule FlipBook
	*book.FlipBook = New(0, (GadgetWidth(0) - 5) * 0.75, GadgetHeight(0) * 0.5, GadgetWidth(0) * 0.5 - 10, GadgetHeight(0) - 20, RGBA(32,32,32,255))
	
	LoadFont(0, "Arial", 8)
	If ReadFile(0, #PB_Compiler_File)
		pageNr = AddPage(*book, #True,RGBA(32, 200, 32, 255), RGBA(255,255,255,255),5)
		StartVectorDrawing(ImageVectorOutput(*book\page(pageNr)))
		AddPathCircle(200,250, 150) 
		VectorSourceColor(RGBA(128, 250, 128, 255))
		StrokePath(15)
		VectorFont(FontID(0))
		ScaleCoordinates(2,2)
		MovePathCursor(-100, 250)
		DrawVectorParagraph("...use mouse to flip the pages!",*book\width,*book\height,#PB_VectorParagraph_Center)
		ScaleCoordinates(2,2)
		VectorSourceColor(RGBA(255, 255, 255, 255))
		MovePathCursor(-145, 40)
		DrawVectorParagraph("FlipBook" + #CRLF$ + "___________" + #CRLF$ + #CRLF$ + "Mr.L",*book\width,*book\height,#PB_VectorParagraph_Center)
		
		StopVectorDrawing()
		AddPage(*book, #True, RGBA(200, 200, 200, 255), RGBA(255,255,255,255), 5)
		While Not Eof(0)
			pageNr = AddPage(*book, #True, RGBA(Random(255,128), Random(255,128), Random(255,128), 255), RGBA(255,255,255,255),20)
			StartVectorDrawing(ImageVectorOutput(*book\page(pageNr)))
			VectorSourceColor(RGBA(0, 0, 0, 255))
			VectorFont(FontID(0))
			y = 25
			AddPathBox(10, 10, *book\width - 20, *book\height - 20)
			ClipPath()
			Repeat
				MovePathCursor(15.5, y+0.5)
				DrawVectorText(ReplaceString(ReadString(0), Chr(9), " "))
				y + VectorTextHeight(" ")
			Until y > *book\height - 65 Or Eof(0)
			MovePathCursor(0, *book\height - 30)
			DrawVectorParagraph("Page " + Str(*book\nrPages - 2), *book\width, 30, #PB_VectorParagraph_Center)
			StopVectorDrawing()
		Wend
		If (*book\nrPages & 1) = 0
			AddPage(*book, #True, RGBA(200, 200, 200, 255), RGBA(255,255,255,255), 5)
		EndIf
		AddPage(*book, #True,RGBA(32, 200, 32, 255), RGBA(255,255,255,255), 5)
	EndIf	
	
	DrawBook(*book, 0, 0, #False)
	
	Repeat
		event = WaitWindowEvent()
		HandleEvent(*book, event)
	Until event = #PB_Event_CloseWindow
CompilerEndIf

Re: FlipBook Module

Posted: Thu Jan 14, 2021 10:41 pm
by STARGÅTE
If you change "If ReadFile(0, #PB_Compiler_Filename)" to "If ReadFile(0, #PB_Compiler_File)" it works.

Very nice code and to my astonishment fest in drawing.
Thx.

Re: FlipBook Module

Posted: Thu Jan 14, 2021 11:19 pm
by Mr.L
STARGÅTE wrote:If you change "If ReadFile(0, #PB_Compiler_Filename)" to "If ReadFile(0, #PB_Compiler_File)" it works.
Great, learned something again. Thank you!
I changed the code accordingly.

Re: FlipBook Module

Posted: Thu Jan 14, 2021 11:21 pm
by infratec
Thump Up!

You can use

Code: Select all

CompilerIf #PB_Compiler_IsMainFile
...
...
CompilerEndIf
for the demo code, then the code can stay always as example included in the file.

Re: FlipBook Module

Posted: Thu Jan 14, 2021 11:38 pm
by ar-s
Very nice effect. Thanks for sharing !

Re: FlipBook Module

Posted: Fri Jan 15, 2021 12:10 am
by Saki
Really very nice. 8)
Make it DPI aware though, otherwise it can't be used with DPI aware codes.

Re: FlipBook Module

Posted: Fri Jan 15, 2021 12:37 am
by Mijikai
Really nice effect.

Re: FlipBook Module

Posted: Fri Jan 15, 2021 9:31 am
by dige
Great job!

Re: FlipBook Module

Posted: Fri Jan 15, 2021 11:35 am
by Sicro
Very cool, thanks a lot. 8)
Mr.L wrote:(the code is free to use, do whatever you want with it)
I would like to add the code to the CodeArchiv so that the code doesn't get lost in the forum. The quoted text is a license to use the code freely, I'm just unclear if you also allow modifications and re-publications. If you don't want to use a common OpenSource license (e.g. the MIT license), can I change the text like this:
"the code is free to use, do whatever you want with it" => "the code is free, do whatever you want with it" :?:

Re: FlipBook Module

Posted: Fri Jan 15, 2021 11:41 am
by kernadec
hi, Mr.L
is beautiful i love the simulation can be change the mouse cursor to finger :D

best regard

Re: FlipBook Module

Posted: Fri Jan 15, 2021 12:36 pm
by Tenaja
Cool!
Thanks for sharing!

Re: FlipBook Module

Posted: Fri Jan 15, 2021 2:36 pm
by Andre
Very cool, thanks for sharing! :D

As suggestion:
One additional good option (similar to the PDF previews on internet sites) would be "hovering back/for arrows" on the left and right side of the book, allowing the flip back/for just by left mouse-click.

Re: FlipBook Module

Posted: Fri Jan 15, 2021 3:00 pm
by davido
@Mr.L ,

Impressive. Thank you for sharing. :D
Works fine on my MacBook Pro M1.

@Andre,
You can use just a mouse click to flip pages.
I think this is a 'Feature'!
Place the mouse pointer about 5cm diagonally from the corner and click firmly and slowly. It is a 'sweet spot' so you may have to search!

Re: FlipBook Module

Posted: Fri Jan 15, 2021 3:26 pm
by Mr.L
thx, everyone!
Sicro wrote:can I change the text like this:
"the code is free to use, do whatever you want with it" => "the code is free, do whatever you want with it" :?:
Yes, you can. There is absolutely no restriction, it's a free giveaway. :D

Re: FlipBook Module

Posted: Fri Jan 15, 2021 5:53 pm
by ebs
kernadec wrote:hi, Mr.L
hi, is beautiful i love the simulation can be change the mouse cursor to finger :D
best regard
Sure, just add one line:

Code: Select all

CanvasGadget(0,0,0,WindowWidth(0), WindowHeight(0))
SetGadgetAttribute(0, #PB_Canvas_Cursor, #PB_Cursor_Hand) ; <= add this line
@Mr.L
Thank you for this great code!